本篇文章接着上篇事物增强器(上篇)继续解析事物增强器的步骤。
回滚处理
上一篇文章已经讲述了目标方法运行前的事务准备工作,而这些准备工作最大的目的无非是对于程序没有按照我们期待的那样去进行,也就是出现特定的错误,那么,当出现错误的时候,Spring是怎样对数据进行恢复的呢?
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { //当抛出异常时首先判断当前是否存在事务,这是基础依据 if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } //判断是否回滚的默认依据是抛出的异常是否是RuntimeException或者Error的类型 if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { try { //根据TransactionStatus信息进行回滚处理 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by rollback exception", ex); throw ex2; } } else { // 如果不满足回滚条件即使抛出异常也同样会提交 try { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by commit exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by commit exception", ex); throw ex2; } } } }
在对目标方法的执行过程中,一旦出现Throwable就会被引导至此方法处理,但是并不代表所有的Throwable都会被回滚处理,比如我们最常用的Exception,默认是不会被处理的。默认情况下,即使出现异常,数据也会被正常提交,而这个关键的地方就是在txInfo.transactionAttribute.rollbackOn(ex)这个函数。
1.回滚条件
public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }
可以看到,默认情况下Spring中的事务异常处理机制只对RuntimeException和Error两种情况感兴趣,当然可以通过扩展类来改变,不过,我们最常用的还是使用事务提供的属性设置,利用注解方式的使用,例如:
@Transactional(progation=Progation.REQUIRED,rollbackFor=Exception.class)
2.回滚处理
当然,一旦符合回滚条件,那么Spring就会将程序引导至回滚处理函数中。
public final void rollback(TransactionStatus status) throws TransactionException { //如果事务已经完成,那么再次回滚会抛出异常 if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; processRollback(defStatus, false); }
private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { boolean unexpectedRollback = unexpected; try { //激活所有TransactionSynchronization中对应的方法 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 { 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"); } // 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; } //激活所有TransactionSynchronization中对应的方法 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 { //清空记录的资源并将挂起的资源恢复 cleanupAfterCompletion(status); } }
同样,对于在Spring中的复杂逻辑处理过程,在入口函数一般都会给出个整体的处理脉络,而把实现细节委托给其他函数去执行。Spring中对于回滚操作处理的大致脉络如下:
(1)首先是自定义触发器的调用,包括在回滚前、完成回滚后的调用,当然完成回滚包括正常回滚与回滚过程中出现异常,自定义的触发器会根据这些信息作进一步处理,而对于触发器的注册,常见是在回调过程中通过TransactionSynchronizationManager类中的静态方法直接注册:
public static void registerSynchronization(TransactionSynchronization synchronization) throws IllegalStateException { Assert.notNull(synchronization, "TransactionSynchronization must not be null"); if (!isSynchronizationActive()) { throw new IllegalStateException("Transaction synchronization is not active"); } synchronizations.get().add(synchronization); }
(2)除了触发监听函数外,就是真正的回滚逻辑处理了。
❤ 当之前已经保存的事务信息中有保存点信息的时候,使用保存点信息进行回滚。常用于嵌入式事务,对于嵌入式的事务处理,内嵌的事务异常并不会引起外部事务的回滚。
根据保存点回滚的实现方式其实是根据底层的数据库连接进行的。
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); }
这里使用的是JDBC的方式进行数据库的连接,那么getSavePointManager函数返回的是jdbc.TransactionObjectSupport,也就是说上面函数会调用JDBCTransactionObjectSupport中的rollbackToSavePoint方法:
public void rollbackToSavepoint(Object savepoint) throws TransactionException { 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); } }
❤ 当之前已经保存的事务信息中的事务为新事务,那么直接回滚。常用于单独事务的处理。对于没有保存点的回滚,Spring同样是使用底层数据库连接提供的API来操作的,由于我们使用的是DataSourceTransactionManager,那么doRollback函数会使用此类中的实现:
protected void doRollback(DefaultTransactionStatus status) { HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); Transaction hibTx = txObject.getSessionHolder().getTransaction(); Assert.state(hibTx != null, "No Hibernate transaction"); if (status.isDebug()) { logger.debug("Rolling back Hibernate transaction on Session [" + txObject.getSessionHolder().getSession() + "]"); } try { hibTx.rollback(); } catch (org.hibernate.TransactionException ex) { throw new TransactionSystemException("Could not roll back Hibernate transaction", ex); } catch (HibernateException ex) { // Shouldn't really happen, as a rollback doesn't cause a flush. throw convertHibernateAccessException(ex); } catch (PersistenceException ex) { if (ex.getCause() instanceof HibernateException) { throw convertHibernateAccessException((HibernateException) ex.getCause()); } throw ex; } finally { if (!txObject.isNewSession() && !this.hibernateManagedSession) { // Clear all pending inserts/updates/deletes in the Session. // Necessary for pre-bound Sessions, to avoid inconsistent state. txObject.getSessionHolder().getSession().clear(); } } }
❤ 当前事务信息中表明是存在事务的,又不属于以上两种情况,多数用于JPA,只做回滚标识,等到提交的时候统一不提交。
3.回滚后的信息清除
对于回滚逻辑执行结束后,无论回滚是否成功,都必须要做的事情就是事务结束后的收尾工作。
private void cleanupAfterCompletion(DefaultTransactionStatus status) { //设置完成状态 status.setCompleted(); if (status.isNewSynchronization()) { TransactionSynchronizationManager.clear(); } if (status.isNewTransaction()) { doCleanupAfterCompletion(status.getTransaction()); } 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()); } }
从上述函数中可以看出,事务的收尾工作包括:
(1)设置状态是对事务信息作完成标识以避免重复调用。
(2)如果当前事务是新的同步状态,需要将绑定到当前线程的事务信息清除。
(3)如果是新事务需要做些清除资源的工作。
(4)如果在事务执行前有事务挂起,那么当事务执行结束后需要将挂起事务恢复。
事务提交
之前我们分析了Spring的事务异常处理机制,那么事务的执行并没有出现任何的异常,也就意味着事务可以走正常的事务提交流程了。
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } }
在真正的数据提交之前,还需要做个判断。在我们分析事务异常处理规则的时候,当某个事务既没有保存点又不是新事务,Spring对它的处理方式只是设置一个回滚标识,这个回滚标识在这里就会派上用场了,主要的应用场景如下:
某个事务是另一个事务的嵌入事务,但是,这些事务又不在Spring的管理范围内,或者无法设置保存点,那么Spring会通过设置回滚标识的方式来禁止提交。首先当某个嵌入式事务发生回滚的时候会设置标识,而等到外部事务提交时,一旦判断出当前事务被设置了回滚标识,则由外部事务来统一进行整体事务的回滚。所以,当事务没有被异常捕获的时候也并不意味着一定会执行提交过程。
事务提交的过程:
在提交事务的过程中也不是直接提交的,而是考虑了诸多的方面,符合提交的条件如下:
❤ 当事务状态中有保存点的信息的话便不会去提交事务;
❤ 当事务非新事务的时候也不会去执行提交事务操作;
此条件主要是考虑内嵌事务的情况,对于内嵌事务,在Spring中正常的处理方式是将内嵌事务开始之前设置保存点,一旦内嵌事务出现异常便根据保存点信息进行回滚,但是如果没有出现异常,内嵌事务并不会单独提交,而是根据事务流由最外层事务负责提交,所以如果当前存在保存点信息便不是最外层事务,不做保存操作,对于是否是新事务的判断也是基于此考虑。
如果程序流通过了事务的层层把关,最后顺利的进入了提交流程,那么同样,Spring会将事务提交的操作引导至底层数据库连接的API,进行事务提交。
protected void doCommit(DefaultTransactionStatus status) { HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); Transaction hibTx = txObject.getSessionHolder().getTransaction(); Assert.state(hibTx != null, "No Hibernate transaction"); if (status.isDebug()) { logger.debug("Committing Hibernate transaction on Session [" + txObject.getSessionHolder().getSession() + "]"); } try { hibTx.commit(); } catch (org.hibernate.TransactionException ex) { // assumably from commit call to the underlying JDBC connection throw new TransactionSystemException("Could not commit Hibernate transaction", ex); } catch (HibernateException ex) { // assumably failed to flush changes to database throw convertHibernateAccessException(ex); } catch (PersistenceException ex) { if (ex.getCause() instanceof HibernateException) { throw convertHibernateAccessException((HibernateException) ex.getCause()); } throw ex; } }
参考:《Spring源码深度解析》 郝佳 编著: