JDBC事务
事务回滚
Spring中事务实现
在Spring中TransactionInterceptor
和PlatformTransactionManager
这两个类是整个事务模块的核心,
TransactionInterceptor
负责拦截方法执行,进行判断是否需要提交或者回滚事务。
PlatformTransactionManager
是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。
我们重点研究下这两个类的源码。
TransactionInterceptor
类中的代码有很多,我简化一下逻辑,方便说明:
//以下代码省略部分内容 public Object invoke(MethodInvocation invocation) throws Throwable { //获取事务调用的目标方法 Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null; //执行带事务调用 return invokeWithinTransaction( invocation.getMethod(), targetClass, invocation::proceed); }
invokeWithinTransaction
简化逻辑如下:
//TransactionAspectSupport.class //省略了部分代码 protected Object invokeWithinTransaction(// Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { Object retVal; try { //调用真正的方法体 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 如果出现异常,执行事务异常处理 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { //最后做一下清理工作,主要是缓存和状态等 cleanupTransactionInfo(txInfo); } //如果没有异常,直接提交事务。 commitTransactionAfterReturning(txInfo); return retVal; }
事务出现异常回滚的逻辑completeTransactionAfterThrowing
如下:
//省略部分代码 protected void completeTransactionAfterThrowing( @Nullable TransactionInfo txInfo, Throwable ex) { // 判断是否需要回滚,判断的逻辑就是看有没有声明事务属性, // 同时判断是不是在目前的这个异常中执行回滚。 if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { //执行回滚 txInfo.getTransactionManager().rollback( txInfo.getTransactionStatus()); } else { //否则不需要回滚,直接提交即可。 txInfo.getTransactionManager().commit( txInfo.getTransactionStatus()); } }
真正执行回滚逻辑的代码中PlatformTransactionManager
接口的子类,
我们以JDBC的事务为例,DataSourceTransactionManager
就是jdbc的事务管理类。
跟踪上面的代码rollback(txInfo.getTransactionStatus())
可以发现最终执行的代码如下:
@Override protected void doRollback(DefaultTransactionStatus status) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Rolling back JDBC transaction on " + "Connection [" + con + "]"); } try { con.rollback(); //调用jdbc的 rollback进行回滚事务。 } catch (SQLException ex) { throw new TransactionSystemException( "Could not roll back JDBC transaction", ex); } }
自己动手简单实现事务
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
新增一个注解:
/** * @description: * @author: luozhou * @create: 2020-03-29 17:05 **/ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MyTransaction { //指定异常回滚 Class<? extends Throwable>[] rollbackFor() default {}; }
新增连接管理器,该类帮助我们管理连接, 该类的核心功能是把取出的连接对象绑定到线程上, 方便在AOP处理中取出,进行提交或者回滚操作:
@Component public class DataSourceConnectHolder { @Autowired DataSource dataSource; /** * 线程绑定对象 */ ThreadLocal<Connection> resources = // new NamedThreadLocal<>("Transactional resources"); public Connection getConnection() { Connection con = resources.get(); if (con != null) { return con; } try { con = dataSource.getConnection(); //为了体现事务,全部设置为手动提交事务 con.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } resources.set(con); return con; } public void cleanHolder() { Connection con = resources.get(); if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } resources.remove(); } }
新增一个切面,这部分是事务处理的核心,先获取注解上的异常类, 然后捕获住执行的异常,判断异常是不是注解上的异常或者其子类, 如果是就回滚,否则就提交。
@Aspect @Component public class MyTransactionAopHandler { @Autowired DataSourceConnectHolder connectHolder; Class<? extends Throwable>[] es; //拦截所有MyTransaction注解的方法 @org.aspectj.lang.annotation.Pointcut( "@annotation(luozhou.top.annotion.MyTransaction)") public void Transaction() { } @Around("Transaction()") public Object TransactionProceed(ProceedingJoinPoint proceed) throws Throwable { Object result = null; Signature signature = proceed.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method == null) { return result; } MyTransaction transaction = method.getAnnotation(MyTransaction.class); if (transaction != null) { es = transaction.rollbackFor(); } try { result = proceed.proceed(); } catch (Throwable throwable) { //异常处理 completeTransactionAfterThrowing(throwable); throw throwable; } doCommit(); //直接提交 return result; } /* 执行回滚,最后关闭连接和清理线程绑定 */ private void doRollBack() { try { connectHolder.getConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } finally { connectHolder.cleanHolder(); } } // 执行提交,最后关闭连接和清理线程绑定 private void doCommit() { try { connectHolder.getConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } finally { connectHolder.cleanHolder(); } } // 异常处理,捕获的异常是目标异常或者其子类, // 就进行回滚,否则就提交事务 private void completeTransactionAfterThrowing(Throwable throwable) { if (es != null && es.length > 0) { for (Class<? extends Throwable> e : es) { if (e.isAssignableFrom(throwable.getClass())) { doRollBack(); } } } doCommit(); } }
测试验证创建一个tb_test表,表结构如下:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for tb_test -- ---------------------------- DROP TABLE IF EXISTS `tb_test`; CREATE TABLE `tb_test` ( `id` int(11) NOT NULL, `email` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; SET FOREIGN_KEY_CHECKS = 1;
编写一个Service。saveTest()
方法调用了2个插入语句,
同时声明了@MyTransaction
事务注解,遇到NullPointerException
就进行回滚,
最后我们执行了除以0操作,会抛出ArithmeticException
。
我们用单元测试看看数据是否会回滚。
@Service public class MyTransactionTest implements TestService { @Autowired DataSourceConnectHolder holder; //一个事务中执行两个sql插入 @MyTransaction(rollbackFor = NullPointerException.class) @Override public void saveTest(int id) { saveWitharamters(id, "luozhou@gmail.com"); saveWitharamters(id + 10, "luozhou@gmail.com"); int aa = id / 0; } //执行sql private void saveWitharamters(int id, String email) { String sql = "insert into tb_test values(?,?)"; Connection connection = holder.getConnection(); PreparedStatement stmt = null; try { stmt = connection.prepareStatement(sql); stmt.setInt(1, id); stmt.setString(2, email); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } }
单元测试
@SpringBootTest @RunWith(SpringRunner.class) class SpringTransactionApplicationTests { @Autowired private TestService service; @Test void contextLoads() throws SQLException { service.saveTest(1); } }
图代码声明了事务对NullPointerException
异常进行回滚,
运行中遇到了ArithmeticException
异常,所以是不会回滚的,
我们在右边的数据库中刷新发现数据正常插入成功了,说明并没有回滚。
我们把回滚的异常类改为ArithmeticException
,把原数据清空再执行一次,
出现了ArithmeticException
异常,这个时候查看数据库是没有记录新增成功了,
这说明事物进行回滚了,表明我们的注解起作用了。