使用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(?)
複製程式碼
但是資料庫沒有資料
到此證明事物攔截成功,可以使用spring來管理ActiveRecordPlugin的事物了
去掉throw new RuntimeException("test");或者JFinalTxAop.setRollbackOnly()
的效果
sql
Sql: insert into `test`(`id`) values(?)
複製程式碼