rollback 流程
如果在执行过程中,发生了异常,执行方法:completeTransactionAfterThrowing->AbstractPlatformTransactionManager#rollback
,以下代码是具体处理异常时回滚的源码:
private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { // 意外的回滚 boolean unexpectedRollback = unexpected; try { // 回滚完成前回调 triggerBeforeCompletion(status); // 有保存点回滚到保存点 if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); } // 当前状态是一个新事务 else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } // 进行回滚 doRollback(status); } else { // 内层事务为 REQUIRED 传播机制时会走这里,设置全局的回滚标记 if (status.hasTransaction()) { if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { if (status.isDebug()) { logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); } //设置连接要回滚标记,也就是全局回滚 doSetRollbackOnly(status); } else { if (status.isDebug()) { logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); } } } else { logger.debug("Should roll back transaction but cannot - no transaction available"); } if (!isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = false; } } } catch (RuntimeException | Error ex) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw ex; } // 回滚完成后回调 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); // 存在全局的回滚标记导致抛出如下异常 if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } } finally { // 根据事务状态信息,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等 cleanupAfterCompletion(status); } }
unexpected 参数这个一般是 false,除非是设置 rollback-only=true
才是 true 或者是内层异常了给予了回滚标记
- 如外层事务传播特性是 REQUIRED、内层事务传播特性也是 REQUIRED,表示是全局的回滚标记
- 首先会在回滚之前进行回调,然后判断是否设置了保存点,比如 NESTED 传播特性会设置,要先回滚到保存点,如果状态是新的事务,那就进行回滚,如果不是新的,就设置一个回滚标记,内部是设置连接持有器的回滚标记
- 最后回滚完成回调,根据事务状态信息,完成后数据清除和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
事务执行结果测试
通过不同的传播特性机制来演示,外层事务的传播特性为标题所标注的,然后内层事务通过设置不同的传播特性机制进行事务测试
代码
Dao、Service 交互代码
public class BookDao { JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 减库存,减去某本书的库存 * * @param id */ public void updateStock(int id) { try { String sql = "update book_stock set stock=stock-1 where id=?"; jdbcTemplate.update(sql, id); for (int i = 1; i >= 0; i--) System.out.println(10 / i); }catch (Exception e) { } } }
public class BookService { BookDao bookDao; public BookDao getBookDao() { return bookDao; } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } /** * 结账:传入哪个用户买了哪本书 */ public void checkout(String username, int id) { try { bookDao.updateStock(id); } catch (Exception e) { e.printStackTrace(); } } }
XML 配置文件
<context:property-placeholder location="classpath:dbconfig.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driverClassName}"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" > <constructor-arg name="dataSource" ref="dataSource"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="bookService" class="com.vnjohn.tx.xml.service.BookService"> <property name="bookDao" ref="bookDao"/> </bean> <bean id="bookDao" class="com.vnjohn.tx.xml.dao.BookDao"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <aop:config> <aop:pointcut id="txPoint" expression="execution(* com.vnjohn.tx.xml.*.*.*(..))"/> <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/> </aop:config> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="checkout" propagation="REQUIRED"/> <tx:method name="updateStock" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
核心 Test 入口类
public class TxTest { public static void main(String[] args) throws SQLException { System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"d:\\code"); ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml"); BookService bookService = context.getBean("bookService", BookService.class); bookService.checkout("vnjohn",1); } }
REQUIRED
- 内层事务:MANDATORY、REQUIRED、SUPPORTS,执行结果如下:
如果程序正常执行,那么内层事务不会提交,在外部事务中统一进行事务提交 如果内层事务,或者外层事务中出现异常情况,那么会在外层事务的处理中统一进行异常回滚 - 内层事务:NEVER,执行结果如下:
外层方法不能出现事务,如果出现事务则直接报错 - 内层事务:NOT_SUPPORTED,执行结果如下:
1、外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
2、若内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
3、同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚 - 内层事务:REQUIRED_NEW,执行结果如下:
1、若外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务
2、若程序正常执行,则内层方法优先事务提交,然后外层方法再提交
3、若内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
4、若外层方法中存在异常,那么内层事务正常提交,而外层方法会进行回滚操作 - 内层事务:NESTED,执行结果如下:
1、若外层方法中有事务,那么直接创建一个保存点,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
2、若内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
3、若外层方法中存在异常情况,那么会内层方法会正常执行,并且执行完毕之后释放保存点,并且外层方法事务会进行回滚
MANDATORY
内层事务不管是那种传播机制,其执行结果都是一样的,MANDATORY 不可以作为外层事务,在运行的时候必须需要一个事务,如果没有事务,会抛出异常
SUPPORTS
- 内层事务:MANDATORY,执行结果如下:
外层方法中如果不包含事务的话,那么内层方法在获取事务对象的时候直接报错,而外层方法中不包含事务,所以无需回滚 - 内层事务:REQUIRED,执行结果如下:
1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常 - 内层事务:SUPPORTS、NEVER、NOT_SUPPORTED,执行结果如下:
1、内外层方法都不包含事务的话,会以无事务的方法开始运行,每个数据库操作直接执行即可
2、如果出现异常情况,则后续的操作不会执行,但已经执行过的数据库操作不受任何影响 - 内层事务:REQUIRED_NEW、NESTED,执行结果如下:
1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常
NEVER
- 内层事务:MANDATORY,执行结果如下:
1、外层方法中如果不包含事务的话,那么内层方法在获取事务对象的时候直接报错,而外层方法中不包含事务,所以无需回滚 - 内层事务:REQUIRED,执行结果如下:
1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常 - 内层事务:SUPPORTS、NEVER、NOT_SUPPORTED,执行结果如下:
1、内外层方法都不包含事务的话,会以无事务的方法开始运行,每个数据库操作直接执行即可
2、若出现异常情况,则后续的操作不会执行,但已经执行过的数据库操作不受任何影响 - 内层事务:REQUIRED_NEW、NESTED,执行结果如下:
1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常
NOT_SUPPORTED
- 内层事务:MANDATORY,执行结果如下:
外层方法中如果不包含事务的话,那么内层方法在获取事务对象的时候直接报错,而外层方法中不包含事务,所以无需回滚 - 内层事务:REQUIRED,执行结果如下:
1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常 - 内层事务:SUPPORTS、NEVER、NOT_SUPPORTED,执行结果如下:
1、内外层方法都不包含事务的话,会以无事务的方法开始运行,每个数据库操作直接执行即可
2、若出现异常情况,则后续的操作不会执行,但已经执行过的数据库操作不受任何影响 - 内层事务:REQUIRED_NEW、NESTED,执行结果如下:
1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常
REQUIRED_NEW
- 内层事务:MANDATORY、REQUIRED、SUPPORTS,执行结果如下:
1、若程序正常执行,那么内层事务不会提交,在外部事务中统一进行事务提交
2、若内层事务,或者外层事务中出现异常情况,那么会在外层事务的处理中统一进行异常回滚 - 内层事务:NEVER,执行结果如下:
外层方法不能出现事务,如果出现事务则直接报错 - 内层事务:NOT_SUPPORTED,执行结果如下:
1、外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
2、若内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
3、同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚 - 内层事务:REQUIRED_NEW,执行结果如下:
1、若外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务
2、若程序正常执行,则内层方法优先事务提交,然后外层方法再提交
3、若内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
4、若外层方法中存在异常,那么内层事务正常正常提交,而外层方法会进行回滚操作 - 内层事务:NESTED,执行结果如下:
1、若外层方法中有事务,那么直接创建一个保存点
2、若外层方法没有事务,那么就创建一个新的事务,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
3、若内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
4、若外层方法中存在异常情况,那么内层方法不会正常执行,只是在执行完毕之后释放保存点「因为 NESTED 使用的是同一个事务」,然后统一在外层方法中会进行事务的回滚
NESTED
- 内层事务:MANDATORY、REQUIRED、SUPPORTS,执行结果如下:
1、若程序正常执行,那么内层事务不会提交,在外部事务中统一进行事务提交
2、若内层事务或者外层事务中出现异常情况,那么会在外层事务的处理中统一进行异常回滚 - 内层事务:NEVER,执行结果如下:
1、外层方法不能出现事务,如果出现事务则直接报错 - 内层事务:NOT_SUPPORTED,执行结果如下:
1、外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
2、若内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
3、同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚 - 内层事务:REQUIRED_NEW,执行结果如下:
1、若外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务及连接
2、若程序正常执行,则内层方法优先事务提交,然后外层方法再提交
3、若内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
4、若外层方法中存在异常,那么内层事务正常提交,而外层方法会进行回滚操作 - 内层事务:NESTED,执行结果如下:
1、若外层方法中有事务,那么直接创建一个保存点,如果外层方法没有事务,那么就创建一个新的事务,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
2、若内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
3、若外层方法中存在异常情况,那么内层方法会执行回滚,并且执行完毕之后释放保存点,并且外层方法事务也会进行回滚
FAQ
REQUIRED 和 NESTED 回滚的区别?
在回答两种方式区别的时候,最大的问题在于保存点的设置,你可能会认为内部设置 REQUIRED 和 NESTED 效果是一样的,其实在外层方法对内层方法的异常情况在进行捕获的时候区别很大,两者报的异常信息都不同,使用 REQUIRED 的时候,会报
Transaction rolled back because it has been marked as rollback-only
信息,因为内部异常了,设置了回滚标记,外部捕获之后,要进行事务的提交,此时发现有回滚标记,那么意味着要回滚,所以会报异常,而 NESTED 不会发生这种情况,因为在回滚的时候把回滚标记清除了,外部捕获异常后去提交,没发现回滚标记,就可以正常提交了
REQUIRED_NEW 和 NESTED 区别
这两种方式产生的效果是一样的,但是 REQUIRED_NEW 会有新的连接生成,而 NESTED 使用的是当前事务的连接,而且 NESTED 还可以回滚到保存点,REQUIRED_NEW 每次都是一个新的事务,没有办法控制其他事务的回滚,但 NESTED 其实是一个事务,外层事务可以控制内层事务的回滚,内层就算没有异常,外层出现异常,也可以全部回滚
总结
一般在面试过程中经常会问到这种事务相关的笔试题,让你现场进行判别事务是否正常提交还是回滚,所以这里面将所有涉及到的传播特性机制的事务特点都演示了一遍,主要还是要自己去动手断点观察!
如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!