你还不知道Spring之事务的滚回和提交原理,这篇文章带你走进源码级别的解读

文章详细分析了Spring框架中JDBC事务管理的回滚机制,包括savepoint、新事务和已有事务的处理,以及提交流程中的判断逻辑。同时介绍了事务清理工作的步骤,包括释放连接和恢复挂起的事务。
摘要由CSDN通过智能技术生成
  • 源码分析

这里使用的是JDBC的方式进行的数据库连接,那么getSavepointManager()函数返回的是JdbcTransactionObjectSupport,也就是说上面函数会调用JdbcTransactionObjectSupport 中的 rollbackToSavepoint 方法。

接下来查看一下JdbcTransactionObjectSupport 中的 rollbackToSavepoint 方法。

  • 看源码(JdbcTransactionObjectSupport.java)

@Override

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,所以AbstractPlatformTransactionManager里的processRollback函数里的doRollback(status);也就是在DataSourceTransactionManager实现的。

  • 看源码(DataSourceTransactionManager.java)

@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 {

con.rollback();

}

catch (SQLException ex) {

throw translateException(“JDBC rollback”, ex);

}

}

当前事务信息中表明是存在事务的,又不属于以上两种情况,只做回滚标识,等到提交的时候再判断是否又回滚标识,下面回滚的时候再介绍,子事务中状态为PROPAGATION_SUPPORTS PROPAGATION_REQUIREDPROPAGATION_MANDATORY回滚的时候将会标记为回滚标识,我们先来看看是怎么标记的。回到AbstractPlatformTransactionManager类的processRollback函数的doSetRollbackOnly(status);

  • 看源码(DataSourceTransactionManager.java)

@Override

protected void doSetRollbackOnly(DefaultTransactionStatus status) {

// 将status中的transaction取出

DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();

if (status.isDebug()) {

logger.debug(“Setting JDBC transaction [” + txObject.getConnectionHolder().getConnection() +

“] rollback-only”);

}

// transaction执行标记回滚

txObject.setRollbackOnly();

}

继续查看一下setRollbackOnly()函数:

  • 看源码(DataSourceTransactionManager.java)

public void setRollbackOnly() {

// 这里将transaction里面的connHolder标记回滚

getConnectionHolder().setRollbackOnly();

}

继续查看setRollbackOnly()的具体实现

  • 看源码(ResourceHolderSupport.java)

public void setRollbackOnly() {

// 将holder中的这个属性设置成true

this.rollbackOnly = true;

}

我们看到将status中的Transaction中的ConnectionHolder的属性rollbackOnly属性设置为true,这里暂时不多考虑,等到下面提交的时候再介绍。

简单小结

简单总结一下AbstractPlatformTransactionManager类的processRollback函数

  • status.hasSavepoint() ** 如果status中有savePoint,只回滚到savePoint!**

  • status.isNewTransaction() 如果status是一个新事务,才会真正去回滚!

  • status.hasTransaction() ** 如果status有事务,将会对staus中的事务标记!**

事务的提交

在事务的执行没有出现任何的异常,也就意味着事务可以走正常事务的提交流程,这里回到流程中,看看TransactionAspectSupport类中的commitTransactionAfterReturning(txInfo);函数具体做了什么

  • 看源码(TransactionAspectSupport.java)

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对它的处理方式只是设置一个回滚标识(具体是在AbstractPlatformTransactionManager的processRollback函数里面)。这个标识在这里就会派上用场了,如果子事务状态是**PROPAGATION_SUPPORTS **、PROPAGATION_REQUIREDPROPAGATION_MANDATORY,将会在外层事务中运行,回滚的时候,并不执行回滚,只是标记一下回滚的状态,当外层事务提交的时候,会先判断ConnectionHolder中的回滚状态,如果已经标记为回滚,则不会提交,而是外层事务进行回滚。(查看一下txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());)的commit方法:

  • 看源码(AbstractPlatformTransactionManager.java)

@Override

public final void commit(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;

// 如果在事务链中已经被标记回滚,那么不会尝试提交事务,直接回滚

if (defStatus.isLocalRollbackOnly()) {

if (defStatus.isDebug()) {

logger.debug(“Transactional code has requested rollback”);

}

processRollback(defStatus, false);

return;

}

if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {

if (defStatus.isDebug()) {

logger.debug(“Global transaction is marked as rollback-only but transactional code requested commit”);

}

// 这里会进行回滚,并且抛出一个异常

processRollback(defStatus, true);

return;

}

// 如果没有被标记回滚之类的,这里才真正判断是否提交

processCommit(defStatus);

}

当事务执行一切都正常的时候,就可以真正的进入到提交流程了。

  • 看源码(AbstractPlatformTransactionManager.java)

private void processCommit(DefaultTransactionStatus status) throws TransactionException {

try {

Boolean beforeCompletionInvoked = false;

try {

Boolean unexpectedRollback = false;

prepareForCommit(status);

triggerBeforeCommit(status);

triggerBeforeCompletion(status);

beforeCompletionInvoked = true;

// 判断是否有savePoint

if (status.hasSavepoint()) {

if (status.isDebug()) {

logger.debug(“Releasing transaction savepoint”);

}

unexpectedRollback = status.isGlobalRollbackOnly();

// 不提交,仅仅是释放savePoint

status.releaseHeldSavepoint();

}

// 判断是否是新事务 else if (status.isNewTransaction()) {

if (status.isDebug()) {

logger.debug(“Initiating transaction commit”);

}

unexpectedRollback = status.isGlobalRollbackOnly();

// 这里才真正去提交!

doCommit(status);

} else if (isFailEarlyOnGlobalRollbackOnly()) {

unexpectedRollback = status.isGlobalRollbackOnly();

}

// Throw UnexpectedRollbackException if we have a global rollback-only

// marker but still didn’t get a corresponding exception from commit.

if (unexpectedRollback) {

throw new UnexpectedRollbackException(

“Transaction silently rolled back because it has been marked as rollback-only”);

}

}

catch (UnexpectedRollbackException ex) {

// can only be caused by doCommit

triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

throw ex;

}

catch (TransactionException ex) {

// can only be caused by doCommit

if (isRollbackOnCommitFailure()) {

doRollbackOnCommitException(status, ex);

} else {

triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);

}

throw ex;

}

catch (RuntimeException | Error ex) {

if (!beforeCompletionInvoked) {

triggerBeforeCompletion(status);

}

doRollbackOnCommitException(status, ex);

throw ex;

}

// Trigger afterCommit callbacks, with an exception thrown there

// propagated to callers but the transaction still considered as committed.

try {

triggerAfterCommit(status);

}

finally {

triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);

}

}

finally {

// 清空记录的资源并将挂起的资源恢复

cleanupAfterCompletion(status);

}

}

  • 源码分析
  1. status.hasSavepoint() 如果status又savePoint,说明此时的事务是嵌套事务NESTED,这个事务外面还有事务,这里不提交,只是释放保存点。这里也可以看出来NESTED的传播行为了

  2. status.isNewTransaction() 如果是新事务才会提交!这里如果是子事务,只有PROPAGATION_NESTED状态才会走到这里提交,也说明了此状态子事务提交和外层事务是隔离的。

  3. 如果是子事务PROPAGATION_SUPPORTSPROPAGATION_REQUIREDPROPAGATION_MANDATORY这几种状态是旧事务,提交的时候将什么都不做,因为它们是运行在外层事务当中,如果子事务没有回滚,将由外层事务一次性提交

如果程序流通过了事务的层层把关,最后顺利的进入了提交流程,那么同样,Spring会将事务提交的操作引导至底层数据库连接的API,进行事务提交。

接下来看一下具体实现也就是AbstractPlatformTransactionManagerprocessCommit函数里的doCommit方法

  • 看源码(DataSourceTransactionManager.java)

@Override

protected void doCommit(DefaultTransactionStatus status) {

DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();

Connection con = txObject.getConnectionHolder().getConnection();

if (status.isDebug()) {

logger.debug(“Committing JDBC transaction on Connection [” + con + “]”);

}

try {

con.commit();

}

catch (SQLException ex) {

throw translateException(“JDBC commit”, ex);

}

}

从回滚和提交的逻辑看,只有status是新事务,才会进行提交或回滚,需要读者记好这个状态->是否是新事务

事务的清理工作

关于清理的工作我们继续回到AbstractPlatformTransactionManagerprocessCommit函数,在这个函数里面可以看到的是无论是在异常还是没有异常的流程中,最后的finally代码块中的都会执行这个cleanupAfterCompletion(status);方法

  • 看源码(AbstractPlatformTransactionManager.java)

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());

}

}

那么如果是新事务呢,它会做哪些清除资源的操作呢?接下看一下cleanupAfterCompletion函数里的doCleanupAfterCompletion函数的具体实现:

  • 看源码(DataSourceTransactionManager.java)

@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();

}

  • 源码分析

综上cleanupAfterCompletiondoCleanupAfterCompletion这两个方法我们可以知道的是如果在事务执行前有事务挂起,那么当前事务执行结束后需要将挂起的事务恢复。

如果有挂起的事务的话,status.getSuspendedResources()!=null为真,也就是说status中会有suspendedResource这个属性,取得status中的transaction后计入resume方法

  • 看源码(AbstractPlatformTransactionManager.java)

protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)

throws TransactionException {

if (resourcesHolder != null) {

Object suspendedResources = resourcesHolder.suspendedResources;

总结

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。


经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

蚂蚁金服5面,总结了49个面试题,遇到的面试官都是P7级别以上

也就是说status中会有suspendedResource这个属性,取得status中的transaction后计入resume方法

  • 看源码(AbstractPlatformTransactionManager.java)

protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)

throws TransactionException {

if (resourcesHolder != null) {

Object suspendedResources = resourcesHolder.suspendedResources;

总结

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。


经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

[外链图片转存中…(img-bzuED8iP-1714626017090)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值