1.引
上一节分析了嵌套事物的创建,本节分析每种传播特性的回滚处理过程。由于这一部分的组合情况会很多,我们只分析其中的一两种情况,更多的大家还是要多看源码、多测试! 注意:这里最外层的事物一定要开启,如果将最外层的事物特性设置为PROPAGATION_NOT_SUPPORTED,则不会引发嵌套事物的问题。
2.processRollback回顾
private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { boolean unexpectedRollback = unexpected; try { // 1.事物完成之前的触发器调用 triggerBeforeCompletion(status); // 2.如果有保存点,则调用rollbackToHeldSavepoint回滚到保存点 if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); } // 3.如果当前事物是一个新的事物,则调用doRollback执行给定事物的回滚 else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } doRollback(status); } else { // Participating in larger transaction // 4.如果当前事物并非独立事物,则将当前事物的rollbackOnly属性标记为true,等到事物链完成之后,一起执行回滚 // 如果当前存在事物,但是 // 事物的rollbackOnly属性已经被标记为true // 或者globalRollbackOnParticipationFailure(返回是否仅在参与事务失败后才将现有事务全局标记为回滚)为true if (status.hasTransaction()) { if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { if (status.isDebug()) { logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); } System.out.println("==当前事物并非独立事物,且RollbackOnly为true\n"); // 则将ConnectionHolder中的rollbackOnly标记为true doSetRollbackOnly(status); } else { if (status.isDebug()) { logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); } } } // 5.如果当前不存在事物,则不会回滚 // 例如配置了 @Transactional(propagation = Propagation.NOT_SUPPORTED) else { logger.debug("Should roll back transaction but cannot - no transaction available"); } // Unexpected rollback only matters here if we're asked to fail early if (!isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = false; } } } catch (RuntimeException | Error ex) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw ex; } // 6.事物完成之后的触发器调用 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); // Raise UnexpectedRollbackException if we had a global rollback-only marker if (unexpectedRollback) { throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only"); } } finally { //7.事物完成后清理资源 cleanupAfterCompletion(status); } }
3.NOT_SUPPORTED
BankService、PersonService、AccountService的事物传播特性依次是:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void save() throws RuntimeException { System.out.println("==调用BankService的save方法\n"); System.out.println("==准备调用PersonService的save方法\n"); personService.save(); System.out.println("==准备调用PersonService的save方法\n"); accountService.save(); throw new RuntimeException("==AccountService的save方法手动抛出异常"); } @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public void save() throws RuntimeException { System.out.println("==调用PersonService的save方法\n"); jdbcTemplate.update(insert_sql); throw new RuntimeException("==PersonService手动抛出异常"); } @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public void save() throws RuntimeException { System.out.println("==调用AccountService的save方法\n"); jdbcTemplate.update(insert_sql); throw new RuntimeException("==AccountService的save方法手动抛出异常"); }
该特性下PersonService、AccountService并不会开启事物,在processRollback方法中会走到第6点,所以即便PersonService、AccountService抛出异常,也不会回滚。但是最外层的BankService是开启事物的,所以如果BankService里有针对数据库的写操作并抛出异常,依然会回滚。
但是这里是嵌套事物的回滚,当内层事物回滚之后不要忘记,外层事物还处在被挂起的状态,那么外层被挂起的事物如何恢复呢?就在processRollback方法中的第7点。
4.恢复挂起事物
private void cleanupAfterCompletion(DefaultTransactionStatus status) { // 1.将当前事物状态标记为已完成 status.setCompleted(); // 2.清除synchronization if (status.isNewSynchronization()) { TransactionSynchronizationManager.clear(); } // 3.事务完成后清理资源。 if (status.isNewTransaction()) { doCleanupAfterCompletion(status.getTransaction()); } // 4.从嵌套事物中恢复被挂起的资源 if (status.getSuspendedResources() != null) { if (status.isDebug()) { logger.debug("Resuming suspended transaction after completion of inner transaction"); } Object transaction = (status.hasTransaction() ? status.getTransaction() : null); resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources()); } }
关于其他的资源清理涉及的代码很多,留在后面介绍,这里我们只看如何恢复被挂起的事物。
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder) throws TransactionException { if (resourcesHolder != null) { Object suspendedResources = resourcesHolder.suspendedResources; if (suspendedResources != null) { // 恢复挂起资源 doResume(transaction, suspendedResources); } List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations; // 恢复挂起的事物同步回调接口 if (suspendedSynchronizations != null) { TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel); TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly); TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name); doResumeSynchronization(suspendedSynchronizations); } } } protected void doResume(@Nullable Object transaction, Object suspendedResources) { TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources); } public static void bindResource(Object key, Object value) throws IllegalStateException { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Assert.notNull(value, "Value must not be null"); Map<Object, Object> map = resources.get(); // set ThreadLocal Map if none found if (map == null) { map = new HashMap<>(); resources.set(map); } Object oldValue = map.put(actualKey, value); // Transparently suppress a ResourceHolder that was marked as void... if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null; } if (oldValue != null) { throw new IllegalStateException("Already value [" + oldValue + "] for key [" +actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); } if (logger.isTraceEnabled()) { logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +Thread.currentThread().getName() + "]"); } }
该方法比较简单,获取被挂起事物后重新绑定到resources对象即可。resources的定义如下:
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
5.PROPAGATION_REQUIRES_NEW
该情况下,因为PROPAGATION_REQUIRES_NEW每次都会新建事物,并挂起已有事物,所以在该传播特性下,如果PersonService或AccountService发生异常,就会执行回滚操作。当PersonService或AccountService完成回滚之后,恢复并回滚之前挂起的事物即可。至于外层被挂起的事物会不会回滚,则要根据外层事物的传播特性而定。假如外层事物的传播特性为NOT_SUPPORTED,那么即便外层事物操作了数据库并抛出异常也不会被回滚。而且不会有嵌套事物的问题。
6.PROPAGATION_NESTED
该特性下就有可能会走到processRollback方法的弟2步,即回滚到保存点。前提是外层开启事物,且内层事物必须有异常抛出。针对本例即BankService必须开启事物,PersonService或AccountService抛出异常。如果PersonService或AccountService没有抛出异常,虽然两者存在保存点,但是并不会回滚,这样一来即便BankService抛出异常并回滚也不会回滚到保存点,因为保存点是建立在PersonService和AccountService对应的事物上的。
下面来看回滚到保存点的实现:
/** * 回滚到事物的保存点并释放保存点资源 * Roll back to the savepoint that is held for the transaction * and release the savepoint right afterwards. */ public void rollbackToHeldSavepoint() throws TransactionException { Object savepoint = getSavepoint(); if (savepoint == null) { throw new TransactionUsageException("Cannot roll back to savepoint - no savepoint associated with current transaction"); } // 回滚到保存点 getSavepointManager().rollbackToSavepoint(savepoint); // 释放保存点资源 getSavepointManager().releaseSavepoint(savepoint); setSavepoint(null); } public void rollbackToSavepoint(Object savepoint) throws TransactionException { // 回滚到保存点并重置RollbackOnly属性 ConnectionHolder conHolder = getConnectionHolderForSavepoint(); try { conHolder.getConnection().rollback((Savepoint) savepoint); conHolder.resetRollbackOnly(); } catch (Throwable ex) { throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex); } } public void releaseSavepoint(Object savepoint) throws TransactionException { ConnectionHolder conHolder = getConnectionHolderForSavepoint(); try { // 释放保存点资源 conHolder.getConnection().releaseSavepoint((Savepoint) savepoint); } catch (Throwable ex) { logger.debug("Could not explicitly release JDBC savepoint", ex); } }
7.PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED
对于这两种特性,既不会开启一个新的事物,也不会在原有事物中嵌套运行,而是把本身的事物交给已有事物。该情况下会执行到processRollback方法的第4点,将rollbackOnly标记为true,等到最外层事物事物回滚的时候一起回滚。即使最外层的事物没有抛出异常,内层事物的异常也会被外层事物截获并将整个事物进行回滚。
8.总结
关于嵌套事物的处理,就先分析到这里,大家还是要多结合事物创建过程、事物传播特性等多多分析。