<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> 复制代码
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; } } 复制代码
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(); } } 复制代码
@ 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: insert into `test`(`id`) values(?) 复制代码
到此证明事物拦截成功,可以使用spring来管理ActiveRecordPlugin的事物了
去掉 throw new RuntimeException("test");或者JFinalTxAop.setRollbackOnly()
的效果
Sql: insert into `test`(`id`) values(?) 复制代码