使用spring AOP代理
这里使用springboot来实现,spring同理 使用注解@Aspect
maven 依赖
org.springframework.boot spring-boot-starter-aop 复制代码 com.zaxxer HikariCP
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@Componentpublic 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 @Servicepublic 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(?)复制代码