博客
关于我
Spring源码分析十八:事务实现④ - 事务的回滚
阅读量:503 次
发布时间:2019-03-07

本文共 12795 字,大约阅读时间需要 42 分钟。

文章目录

一、前言

本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。


由于 事务的源码和 前篇的 Aop 源码逻辑很类似,所以本篇中某些内容不会展开去讲解,建议先阅读完 全集目录中的Aop部分,再来阅读本文会更好理解。

全集目录:


这是一个巨长的篇章…

全集目录如下:


在 中,解析了关事务的代理创建过程,同时在上篇中讲述了 事务的创建过程,本文就开始讲解一下 事务的回滚过程。

二、事务的回滚 - completeTransactionAfterThrowing

在 中我们分析了TransactionAspectSupport#invokeWithinTransaction 方法完成了事务的增强调用 ,而completeTransactionAfterThrowing 的实现是 TransactionAspectSupport#completeTransactionAfterThrowing。在出现异常的时候,通过该方法进行事务的回滚。

下面我们来看具体代码实现 :

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类型			// 即 return (ex instanceof RuntimeException || ex instanceof Error);			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {   				try {   					// 执行回滚操作					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 {   				// We don't roll back on this exception.				// Will still roll back if TransactionStatus.isRollbackOnly() is true.				try {   					// 如果不满足回滚条件,则还是会提交,也就是说如果抛出异常不是 RuntimeException 或 error类型,则不会触发事务的回滚。					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;				}			}		}	}

上面可以看到,触发回滚操作的判断条件是

txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)

txInfo.transactionAttribute != null 自不必说,表示必须存在事务属性信息,即是事务方法。txInfo.transactionAttribute.rollbackOn(ex)默认的实现如下:

public boolean rollbackOn(Throwable ex) {   		return (ex instanceof RuntimeException || ex instanceof Error);	}

也就是说只有当异常类型是 Error或者 RuntimeException 才会进行回滚。

这里需要注意,如果抛出了其他类型异常,那么并不代表事务没有启用,而是回滚没有触发。


我们下面看看回滚和提交的具体操作:

1. AbstractPlatformTransactionManager#rollback

我们下面来看看 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); 回滚的具体操作即即 中的 AbstractPlatformTransactionManager#rollback 方法

@Override	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 中对应的方法 beforeCompletion() 方法				triggerBeforeCompletion(status);				// 如果当前事务有保存点,也就是当前事务为单独的线程则会退到保存点				if (status.hasSavepoint()) {   					status.rollbackToHeldSavepoint();				}				else if (status.isNewTransaction()) {   					// 如果当前事务为独立的新事物,则直接回退					doRollback(status);				}				else {   					// Participating in larger transaction					if (status.hasTransaction()) {   						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {   							// 如果当前事务不是独立的事务,那么只能标记状态,等到事务链执行完毕后统一回滚							doSetRollbackOnly(status);						}						else {   												}					}					else {   					}					// 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 中对应的方法 afterCompletion() 方法			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);		}	}

我们可以简单总结一下整个脉络:

  1. 首先是自定义触发器的调用,这里是Spring提供的回滚扩展点,包括在回滚前、回滚成功、回滚失败的触发器调用,自定义触发器会根据这些信息作出进一步的处理。而对于触发器的注册,常见的是在回调过程中通过 TransactionSynchronizationManager#registerSynchronization 方法完成,
  2. 除了触发监听函数外,就是真正的回滚逻辑处理了,而这里的具体回滚操作都委托给了底层数据库连接提供的API 来操作的。回滚的基本策略是:
    1. 当之前已经保存的事务信息中有保存点信息的时候,使用保存点信息回滚。常用于嵌入式事务,对于嵌入式事务的处理,内嵌的事务异常并不会引起外部事物的回滚。
    2. 对于之前没有保存的事务信息中的事务为新事物,那么直接回滚。常用语单独事务的处理,对于没有保存点的回滚,Spring同样是使用底层数据库连接提供的API 来操作的。我们这里则是 DataSourceTransactionManager#doRollback
    3. 当前事务信息标明是存在事务的,又不属于以上两种情况,多数用于 JTA,只做回滚标识,等到提交的时候统一不提交。
  3. 回滚后的信息清除。对于回滚逻辑执行结束后,无论回滚是否陈宫,都必须要将信息清除。

下面我们来详细看看上面三点:

1.1. 自定义触发器的调用

自定义触发器的调用,包括在回滚前、回滚成功、回滚失败的触发器调用。实现逻辑都类似,我们这里挑一个回滚前的触发器看一看逻辑:

protected final void triggerBeforeCompletion(DefaultTransactionStatus status) {   		if (status.isNewSynchronization()) {   			if (status.isDebug()) {   				logger.trace("Triggering beforeCompletion synchronization");			}			// 触发前置操作			TransactionSynchronizationUtils.triggerBeforeCompletion();		}	}

TransactionSynchronizationUtils.triggerBeforeCompletion(); 的代码如下:

public static void triggerBeforeCompletion() {   		// 获取所有TransactionSynchronization 触发 beforeCompletion 方法		for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {   			try {   				synchronization.beforeCompletion();			}			catch (Throwable tsex) {   				logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex);			}		}	}

triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 与之逻辑类似,这里就不再看了。

1.2. 回滚逻辑处理

回滚操作最底层的操作都是委托给了数据库API 来实现,所以这里并没有负责的逻辑。

回滚逻辑上面也说了有三种情况,下面我们来一个一个看一看:

1.2.1. status.rollbackToHeldSavepoint();

status.rollbackToHeldSavepoint(); 的实现在 AbstractTransactionStatus#rollbackToHeldSavepoint 中。

处理情况是 :当之前已经保存的事务信息中有保存点信息的时候,使用保存点信息回滚。常用于嵌入式事务,对于嵌入式事务(并非是嵌套service事务)的处理,内嵌的事务异常并不会引起外部事物的回滚

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");		}		// 回滚到 savepoint 		getSavepointManager().rollbackToSavepoint(savepoint);		// 释放保存点		getSavepointManager().releaseSavepoint(savepoint);		setSavepoint(null);	}

这里使用的是 JDBC 的方式进行数据库了连接,所以这里调用的是 getSavepoint(); 返回的是 JdbcTransactionObjectSupport类型。所以这里我们来看 JdbcTransactionObjectSupport 中的

@Override	public void rollbackToSavepoint(Object savepoint) throws TransactionException {   		ConnectionHolder conHolder = getConnectionHolderForSavepoint();		try {   			// 直接调用 Connection的rollback 方法			conHolder.getConnection().rollback((Savepoint) savepoint);			conHolder.resetRollbackOnly();		}		catch (Throwable ex) {   			throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);		}	}	@Override	public void releaseSavepoint(Object savepoint) throws TransactionException {   		ConnectionHolder conHolder = getConnectionHolderForSavepoint();		try {   			// 直接调用 Connection的releaseSavepoint方法			conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);		}		catch (Throwable ex) {   			logger.debug("Could not explicitly release JDBC savepoint", ex);		}	}

1.2.2. doRollback(status);

doRollback(status); 的实现在 DataSourceTransactionManager#doRollback 中。

处理情况是 :对于之前没有保存的事务信息中的事务为新事物,那么直接回滚。常用语单独事务的处理,对于没有保存点的回滚,Spring同样是使用底层数据库连接提供的API 来操作的。

@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 {   			// 直接调用rollback方法 回滚			con.rollback();		}		catch (SQLException ex) {   			throw new TransactionSystemException("Could not roll back JDBC transaction", ex);		}	}

1.2.3. doSetRollbackOnly(status);

doSetRollbackOnly(status); 的实现在DataSourceTransactionManager#doSetRollbackOnly 中。

处理情况: 当前事务信息标明是存在事务的,又不属于以上两种情况,多数用于 JTA(Java Transaction API),只做回滚标识,等到提交的时候统一不提交。即当外围事务进行提交时,发现内嵌的事务中 rollbackOnly 被设置成true。则直接进行回滚,而不再尝试提交

@Override	protected void doSetRollbackOnly(DefaultTransactionStatus status) {   		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();		if (status.isDebug()) {   			logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +					"] rollback-only");		}		txObject.setRollbackOnly();	}

1.3. 回滚信息清除

cleanupAfterCompletion(status); 的实现在 AbstractPlatformTransactionManager#cleanupAfterCompletion 中。

其作用是完成事务回滚的收尾工作,主要包括以下内容 :

  1. 设置状态是对事务信息做完成标识以避免重复调用
  2. 如果当前事务是新的同步状态,需要将绑定到当前线程的事务信息清除
  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.3.1. doCleanupAfterCompletion(status.getTransaction());

doCleanupAfterCompletion(status.getTransaction()); 的实现在DataSourceTransactionManager#doCleanupAfterCompletion 中。在这里完成事务回滚的收尾工作。

@Override	protected void doCleanupAfterCompletion(Object transaction) {   		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;		// Remove the connection holder from the thread, if exposed.		if (txObject.isNewConnectionHolder()) {   			// 将数据库连接从当前线程中解除绑定			TransactionSynchronizationManager.unbindResource(obtainDataSource());		}		// Reset connection.		// 释放连接		Connection con = txObject.getConnectionHolder().getConnection();		try {   			if (txObject.isMustRestoreAutoCommit()) {   				// 恢复数据库连接自动提交属性				con.setAutoCommit(true);			}			// 重置数据连接			DataSourceUtils.resetConnectionAfterTransaction(					con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());		}		catch (Throwable ex) {   			logger.debug("Could not reset JDBC Connection after transaction", ex);		}		if (txObject.isNewConnectionHolder()) {   			if (logger.isDebugEnabled()) {   				logger.debug("Releasing JDBC Connection [" + con + "] after transaction");			}			// 如果当前事务是独立的新创建的事务则在事务完成时释放数据库连接。			DataSourceUtils.releaseConnection(con, this.dataSource);		}		txObject.getConnectionHolder().clear();	}

1.3.2. 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
suspendedSynchronizations = resourcesHolder.suspendedSynchronizations; // 设置事务属性 if (suspendedSynchronizations != null) { TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel); TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly); TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name); doResumeSynchronization(suspendedSynchronizations); } } }

从上面整个回滚流程可以看到,很多的操作,Spring都交给数据库底层实现了,Spring仅仅起到了一个管理的作用。

三、总结

事务的回滚操作,根据不同的情况执行不同的回滚策略。并且在回滚结束之后恢复了挂起的事务(如果有挂起事务)。


以上:内容部分参考

《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

转载地址:http://zykcz.baihongyu.com/

你可能感兴趣的文章
MySQL-redo日志
查看>>
MySQL-【1】配置
查看>>
MySQL-【4】基本操作
查看>>
Mysql-丢失更新
查看>>
Mysql-事务阻塞
查看>>
Mysql-存储引擎
查看>>
mysql-开启慢查询&所有操作记录日志
查看>>
MySQL-数据目录
查看>>
MySQL-数据页的结构
查看>>
MySQL-架构篇
查看>>
MySQL-索引的分类(聚簇索引、二级索引、联合索引)
查看>>
Mysql-触发器及创建触发器失败原因
查看>>
MySQL-连接
查看>>
mysql-递归查询(二)
查看>>
MySQL5.1安装
查看>>
mysql5.5和5.6版本间的坑
查看>>
mysql5.5最简安装教程
查看>>
mysql5.6 TIME,DATETIME,TIMESTAMP
查看>>
mysql5.6.21重置数据库的root密码
查看>>
Mysql5.6主从复制-基于binlog
查看>>