面试官:问你一个,Spring事务是如何传播的?( 六 )

流程和提交是一样的 , 先是判断有没有回滚点 , 如果有就回到到回滚点并清除该回滚点;如果没有则判断是不是新事务(PROPAGATION_REQUIRED属性下的最外层事务和PROPAGATION_REQUIRES_NEW属性下的事务) , 满足则直接回滚当前事务 。 回滚完成后同样需要清除掉当前的事务状态并恢复挂起的连接 。 另外需要特别注意的是在catch里面调用完回滚逻辑后 , 还通过throw抛出了异常 , 这意味着什么?意味着即使是嵌套事务 , 内层事务的回滚也会导致外层事务的回滚 , 也就是addA的事务也会跟着回滚 。 至此 , 事务的传播原理分析完毕 , 深入看每个方法的实现是很复杂的 , 但如果仅仅是分析各个传播属性对事务的影响 , 则有一个简单的方法 。 我们可以将内层事务切面等效替换掉invocation.proceedWithInvocation方法 , 比如上面两个类的调用可以看作是下面这样:
// addA的事务TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try { // addB的事务 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try {retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) {// target invocation exception//事务回滚completeTransactionAfterThrowing(txInfo, ex);throw ex; } finally {cleanupTransactionInfo(txInfo); } //事务提交 commitTransactionAfterReturning(txInfo);}catch (Throwable ex) { //事务回滚 completeTransactionAfterThrowing(txInfo, ex); throw ex;}//事务提交commitTransactionAfterReturning(txInfo);这样看是不是很容易就能分析出事务之间的影响以及是提交还是回滚了?下面来看几个实例分析 。
实例分析我再添加一个C类 , 和addC的方法 , 然后在addA里面调用这个方法 。
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try { // addB的事务 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try {b.addB(); } catch (Throwable ex) {// target invocation exception//事务回滚completeTransactionAfterThrowing(txInfo, ex);throw ex; } //事务提交 commitTransactionAfterReturning(txInfo); // addC的事务 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try {c.addC(); } catch (Throwable ex) {// target invocation exception//事务回滚completeTransactionAfterThrowing(txInfo, ex);throw ex; } //事务提交 commitTransactionAfterReturning(txInfo);}catch (Throwable ex) { //事务回滚 completeTransactionAfterThrowing(txInfo, ex); throw ex;}//事务提交commitTransactionAfterReturning(txInfo);等效替换后就是上面这个代码 , 我们分别来分析 。

  • 都是PROPAGATION_REQUIRED属性:通过上面的分析 , 我们知道三个方法都是同一个连接和事务 , 那么任何一个出现异常则都会回滚 。
  • addB为PROPAGATION_REQUIRES_NEW:如果B中抛出异常 , 那么B中肯定会回滚 , 接着异常向上抛 , 导致A事务整体回滚;如果C中抛出异常 , 不难看出C和A都会回滚 , 但B已经提交了 , 因此不会受影响 。
  • addC为PROPAGATION_NESTED , addB为PROPAGATION_REQUIRES_NEW:如果B中抛出异常 , 那么B回滚并抛出异常 , A也回滚 , C不会执行;如果C中抛出异常 , 先是回滚到回滚点并抛出异常 , 所以A也回滚 , 但B此时已经提交 , 不受影响 。
  • 都是PROPAGATION_NESTED:虽然创建了回滚点 , 但是仍然是同一个连接 , 任何一个发生异常都会回滚 , 如果不想影响彼此 , 可以try-catch生吞子事务的异常实现 。
还有其它很多情况 , 这里就不一一列举了 , 只要使用上面的分析方法都能够很轻松的分析出来 。
总结本篇详细分析了事务的传播原理 , 另外还有隔离级别 , 这在Spring中没有体现 , 需要我们自己结合数据库的知识进行分析设置 。 最后我们还需要考虑声明式事务和编程式事务的优缺点 , 声明式事务虽然简单 , 但不适合用在长事务中 , 会占用大量连接资源 , 这时就需要考虑利用编程式事务的灵活性了 。 总而言之 , 事务的使用并不是一律默认就好 , 接口的一致性和吞吐量与事务有着直接关系 , 严重情况下可能会导致系统崩溃 。
作者:夜勿语
原文链接: