Spring或SpringBoot中管理JFinal ORM外掛事物

Chox.su發表於2019-04-10

使用spring AOP代理

這裡使用springboot來實現,spring同理 使用註解@Aspect

maven 依賴

<dependency>
    <!-- spring boot aop starter依賴 -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 資料來源 -->
<dependency>
	<groupId>com.zaxxer</groupId>
	<artifactId>HikariCP</artifactId>
</dependency>
複製程式碼

JFinalTxAop

package com.syc.common.aop;

import com.jfinal.kit.LogKit;
import com.jfinal.plugin.activerecord.ActiveRecordException;
import com.jfinal.plugin.activerecord.Config;
import com.jfinal.plugin.activerecord.DbKit;
import com.jfinal.plugin.activerecord.NestedTransactionHelpException;
import com.jfinal.plugin.activerecord.tx.TxConfig;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author choxsu
 * @date 2018/4/13
 */
@Aspect
@Component
public class JFinalTxAop {
    /**
     * 是否可以手動提交事物,預設可以自動提交
     */
    private static boolean canCommit = true;
    /**
     * 自定義JFinal 事物註解:類上面
     *
     * @within 表示註解在類下面所有的方法
     */
    @Pointcut("@within(org.springframework.transaction.annotation.Transactional)")
    private void methodWithin() {
    }

    /**
     * 自定義JFinal 事物註解:方法上面
     *
     * @annotation 表示註解只能支援方法上
     */
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    private void methodAnno() {
    }

    /**
     * 相容@Transactional可以放在類上和方法上
     * 當放類上時,類中所有方法都支援事物註解,
     * 如果類上沒有@Transactional,然而是放在方法上的,那麼只有此方法支援事物註解
     *
     * @param pjp 切入點目標物件
     * @return 返回切入方法的返回資料
     * @throws Throwable
     */
    @Around(value = "methodWithin() || methodAnno()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Object retVal = null;
        Config config = getConfigWithTxConfig(pjp);
        if (config == null)
            config = DbKit.getConfig();

        Connection conn = config.getThreadLocalConnection();
        // Nested transaction support
        if (conn != null) {
            try {
                if (conn.getTransactionIsolation() < getTransactionLevel(config))
                    conn.setTransactionIsolation(getTransactionLevel(config));
                retVal = pjp.proceed();
                return retVal;
            } catch (SQLException e) {
                throw new ActiveRecordException(e);
            }
        }
        Boolean autoCommit = null;
        try {
            conn = config.getConnection();
            autoCommit = conn.getAutoCommit();
            config.setThreadLocalConnection(conn);
            conn.setTransactionIsolation(getTransactionLevel(config));// conn.setTransactionIsolation(transactionLevel);
            conn.setAutoCommit(false);
            retVal = pjp.proceed();
            if (canCommit) {
                conn.commit();
            } else {
                try {
                    conn.rollback();
                } catch (Exception e1) {
                    LogKit.error(e1.getMessage(), e1);
                }
            }
        } catch (NestedTransactionHelpException e) {
            if (conn != null) try {
                conn.rollback();
            } catch (Exception e1) {
                LogKit.error(e1.getMessage(), e1);
            }
            LogKit.logNothing(e);
        } catch (Throwable t) {
            if (conn != null) try {
                conn.rollback();
            } catch (Exception e1) {
                LogKit.error(e1.getMessage(), e1);
            }
            throw t instanceof RuntimeException ? (RuntimeException) t : new ActiveRecordException(t);
        } finally {
			canCommit = true;
            try {
                if (conn != null) {
                    if (autoCommit != null)
                        conn.setAutoCommit(autoCommit);
                    conn.close();
                }
            } catch (Throwable t) {
                // can not throw exception here, otherwise the more important exception in previous catch block can not be thrown
                LogKit.error(t.getMessage(), t);
            } finally {
                // prevent memory leak
                config.removeThreadLocalConnection();
            }
        }
        return retVal;
    }

    /**
     * 獲取配置的事務級別
     *
     * @param config
     * @return
     */
    protected int getTransactionLevel(Config config) {
        return config.getTransactionLevel();
    }

    /**
     * @param pjp
     * @return Config
     */
    public static Config getConfigWithTxConfig(ProceedingJoinPoint pjp) {
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method method = ms.getMethod();
        TxConfig txConfig = method.getAnnotation(TxConfig.class);
        if (txConfig == null)
            txConfig = pjp.getTarget().getClass().getAnnotation(TxConfig.class);

        if (txConfig != null) {
            Config config = DbKit.getConfig(txConfig.value());
            if (config == null)
                throw new RuntimeException("Config not found with TxConfig: " + txConfig.value());
            return config;
        }
        return null;
    }

    public static boolean setRollbackOnly() {
        canCommit = false;
        try {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        } catch (NoTransactionException e) {
            return false;
        }
        return true;
    }
}
複製程式碼

使用

TestController

package com.choxsu.elastic.controller;

import com.choxsu.elastic.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author choxsu
 */
@RestController
@RequestMapping(value = {"/test/v1"})
public class TestController {


    @Autowired
    private TestService testService;

    @GetMapping(value = "/testTran")
    public Object testTran(){
        return testService.testTran();
    }
}

複製程式碼

TestService

@ Transactional可以放在類上或方法上都沒問題

當呼叫了setRollbackOnly方法 記得return,不return的話 後面的業務也會被回滾,所以記得return

package com.choxsu.elastic.service;

import com.choxsu.elastic.config.JFinalTx;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import org.springframework.stereotype.Service;


/**
 * @author choxsu
 */
@Transactional 
@Service
public class TestService {

    /**
     * 事物測試
     *
     * @return
     */
    @Transactional
    public Object testTran() {
        Record record = new Record();
        record.set("id", 10);
        Db.save("test", record);
        if (true) {
            throw new RuntimeException("test");
        }
		//或者手動回滾,不用丟擲異常
		JFinalTxAop.setRollbackOnly()
		
        return Ret.by("msg", "success");
    }
}

複製程式碼
sql執行了
Sql: insert into `test`(`id`) values(?)
複製程式碼
但是資料庫沒有資料

1545492800236.jpeg

到此證明事物攔截成功,可以使用spring來管理ActiveRecordPlugin的事物了

去掉throw new RuntimeException("test");或者JFinalTxAop.setRollbackOnly()的效果

sql
Sql: insert into `test`(`id`) values(?)
複製程式碼
資料庫結果

1545492814226.jpeg

相關文章