当前位置: 首页 > news >正文

Spring事务最佳应用指南(包含:事务传播类型、事务失效场景、使用建议、事务源码分析)

前言

本文主要介绍的是在Spring框架中有关事务的应用方式,以及一些生产中常见的与事务相关的问题、使用建议等。同时,为了让读者能够更容易理解,本文在讲解过程中也会通过源码以及案例等方式进行辅助说明,通过阅读本文不但能够解决在实际生产环境中遇到的与Spring事务相关的问题,也能让你掌握更好的应用方式。

事务的传播类型

我们首先从事务的传播类型开始说起,在Spring中事务传播指的是:一个事务方法调用了另一个事务方法,比如事务方法A在执行过程中又调用了事务方法B,那么对于事务方法B来说,应该如何如何运行?于是,针对这个问题,Spring就定义了七种数据传播类型,基本上就是罗列了所有可能遇到的场景,然后供使用者选择:

  • REQUIRED:支持当前事务,如果当前不存在则新开启一个事务(默认配置)

  • SUPPORTS:支持当前事务,如果当前不存在事务则以非事务方式执行

  • MANDATORY:支持当前事务,如果当前不存在事务则抛出异常

  • REQUIRES_NEW:创建一个新事务,如果当前已存在事务则挂起当前事务

  • NOT_SUPPORTED:以非事务方式执行,如果当前已存在事务则挂起当前事务

  • NEVER:以非事务方式执行,如果当前已存在事务则抛出异常

  • NESTED:如果当前存在事务,则在嵌套事务中执行,否则开启一个新事务

场景分类

事务传播类型前一个方法存在事务前一个方法不存在事务当前一个方法存在事务时,是否与其是同一个事务
REQUIRED使用前一个方法的事务创建一个新的事务
SUPPORTS使用前一个方法的事务按非事务方式执行
MANDATORY使用前一个方法的事务抛出异常
REQUIRES_NEW创建一个新的事务创建一个新的事务
NOT_SUPPORTED以非事务方式执行按非事务方式执行-
NEVER抛出异常按非事务方式执行-
NESTED嵌套方式使用前一个方法的事务创建一个新的事务

如果你还不清楚这些传播类型具体有什么区别也没关系,接下来本节会针对每一种类型分别进行效果演示,以此来帮助你进行理解。

在此之前,先准备一下演示的数据

CREATE TABLE `t1` (`id` int(11) NOT NULL,`c` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_c` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `t2` (`id` int(11) NOT NULL,`c` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

两条更新语句,分别对t1t2两张表进行更新。

<update id="updateT1">update t1 set c = 2 where id = 1
</update>
<update id="updateT2">update t2 set c = 2 where id = 1
</update>

REQUIRED

含义:支持当前事务,如果当前不存在则新开启一个事务(默认配置)。

下面这块代码逻辑为:当调用T1Service中的func方法时,除了要更新t1表数据之外,还会调用t2Service中的func方法,更新t2表,不过,在执行t2Service中的方法时会遇到异常。


@Service
public class T1Service {@Resourceprivate TestMapper testMapper;@Resourceprivate T2Service t2Service;@Transactionalpublic void func() {testMapper.updateT1();t2Service.func();}
}@Service
public class T2Service {@Resourceprivate TestMapper testMapper;@Transactionalpublic void func() {testMapper.updateT2();int i = 1 / 0;}
}

@Transactional默认的传播方式就是REQUIRED,当方法执行到int i = 1 / 0时会抛出异常,此时t1、t2表中的数据都不会被修改,因为这两个方法使用的是同一个事务,所以只要有一个遇到异常,两个更新就都不会成功。

SUPPORTS

含义:支持当前事务,如果当前不存在事务则以非事务方式执行。

t2Servicefunc方法现在没有事务了,t2Servicefunc方法配置上@Transactional(propagation = Propagation.SUPPORTS),当执行int i = 1 / 0时,t1、t2两张表数据都不会回滚。

@Service
public class T1Service {@Resourceprivate TestMapper testMapper;@Resourceprivate T2Service t2Service;// 去掉事务 @Transactionalpublic void func() {testMapper.updateT1();t2Service.func();}
}
@Service
public class T2Service {@Resourceprivate TestMapper testMapper;/*** 数据不会回滚,因为当前没有事务,SUPPORTS会以非事务方式执行* 如果配置成 @Transactional(propagation = Propagation.REQUIRED),则事务会生效 */@Transactional(propagation = Propagation.SUPPORTS)public void func() {testMapper.updateT2();int i = 1 / 0;}
}

MANDATORY

含义:支持当前事务,如果当前不存在事务则抛出异常。

t1Service没有事务时,把t2Servicefunc方法配置为@Transactional(propagation = Propagation.MANDATORY)

// t1Service
public void func() {testMapper.updateT1();t2Service.func();
}// t2Service
@Transactional(propagation = Propagation.MANDATORY)
public void func() {testMapper.updateT2();int i = 1 / 0;
}

异常信息

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:362) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:595) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:382) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14]at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14]

REQUIRES_NEW

含义:创建一个新事务,如果当前已存在事务则挂起当前事务。

t2的数据不会被更新。

// t1Service
public void func() {testMapper.updateT1();t2Service.func();
}// t2Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void func() {testMapper.updateT2();int i = 1 / 0;
}

与REQUIRED有什么区别呢?

如果把抛出异常的地方放到t1Service中。

// t1Service
@Transactional
public void func() {testMapper.updateT1();t2Service.func();int i = 1 / 0;
}// t2Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void func() {testMapper.updateT2();
}

再次执行后,t2的数据不会回滚,t1的数据会回滚,因为t2t1不是一个事务。

NOT_SUPPORTED

含义:以非事务方式执行,如果当前已存在事务则挂起当前事务。

NOT_SUPPORTED的效果就是无论异常是在t1Service还是t2Service都不会回滚t2的数据。

// t1Service
@Transactional
public void func() {testMapper.updateT1();t2Service.func();int i = 1 / 0;
}// t2Service
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void func() {testMapper.updateT2();int i = 1 / 0;
}

NEVER

含义:以非事务方式执行,如果当前已存在事务则抛出异常。

如字面含义,直接抛出异常。

// t1Service
@Transactional
public void func() {testMapper.updateT1();t2Service.func();
}// t2Service
@Transactional(propagation = Propagation.NEVER)
public void func() {testMapper.updateT2();
}
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:413) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:352) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:595) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:382) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.14.jar:5.3.14]at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14]at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14]

如果把t1Service中的事务去掉,则不会抛出不支持事务的异常,同时t2Service自己抛出异常后,数据也是不会回滚的,完全是当做无事务处理。

// t1Service
public void func() {testMapper.updateT1();t2Service.func();
}// t2Service
@Transactional(propagation = Propagation.NEVER)
public void func() {testMapper.updateT2();int i = 1 / 0;
}

NESTED

含义:如果当前存在事务,则在嵌套事务中执行,否则开启一个新事务。

NESTED应该是几种事务传播方式中最难理解的,如果不注意,NESTEDREQUIRED功能看起来则差不多,都可以理解为有事务则加入,没有则新启一个,但实际上NESTEDREQUIRED要更加灵活。

先来看第一个案例,在t1Service中调用t2Service时,对t2Service抛出的异常进行了捕获,并且自己也没有再抛出。


// t1Service
@Transactional
public void func() {testMapper.updateT1();// 以此catch异常的原因是想像你说明,在t1Service的func方法中,是不会因为调用t2Service遇到异常而被回滚的,因此异常已经被catch了。回滚主要是因为使用的是同一个事务。try {t2Service.func();} catch (Exception e) {e.printStackTrace();}
}// t2Service
@Transactional(propagation = Propagation.REQUIRED)
public void func() {testMapper.updateT2();int i = 1 / 0;
}

t2Service配置为REQUIRED时,t1t2都进行了回滚,因为是同一个事务。

但如果t2Service配置为NESTED就不一样了,此时t1则不会回滚。

// t1Service
@Transactional
public void func() {testMapper.updateT1();try {t2Service.func();} catch (Exception e) {e.printStackTrace();}
}// t2Service
@Transactional(propagation = Propagation.NESTED)
public void func() {testMapper.updateT2();int i = 1 / 0;
}

NESTED与REQUIRES_NEW的区别

接下来再来看看NESTEDREQUIRES_NEW的区别。

我们分别给t1Servicet2Service加上一段System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());用来查看一下当前执行的事务。

// t1Service
@Transactional
public void func() {testMapper.updateT1();System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());try {t2Service.func();} catch (Exception e) {e.printStackTrace();}
}
// t2Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void func() {testMapper.updateT2();System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());int i = 1 / 0;
}

输出结果

com.demo.transaction.service.T1Service.func
com.demo.transaction.service.T2Service.func

如果把REQUIRES_NEW替换为NESTED则可以看出实际上还是同一个事务。

com.demo.transaction.service.T1Service.func
com.demo.transaction.service.T1Service.func

也就是说,使用NESTED时,虽然还是同一个事务,但却可以在多个方法中进行控制。

实现原理

两个方法同一个事务没有一起回滚,实际上利用的是savepoint功能,大概方式如下:

-- 主事务
savepoint;
-- 执行主事务代码-- 子事务
savepoint;-- 执行子事务代码-- 子事务提交
commit;-- 执行主事务代码-- 主事务提交
commit;

所以,搞清楚实现方式以后,你就会发现如果是在主事务中抛出异常,那么子事务也会被回滚,就像下面这样:t1t2都会回滚。

// t1Service
@Transactional
public void func() {testMapper.updateT1();t2Service.func();int i = 1 / 0;
}
// t2Service
@Transactional(propagation = Propagation.NESTED)
public void func() {testMapper.updateT2();
}

事务的失效场景

关于事务未生效的问题,也是我们在日常开发中经常会遇到的,这一节总结了一些会导致事务失效的场景,你可以了解一下,以免遇到类似的问题。

异常未抛出

被捕获的异常一定要抛出,否则是不会回滚的。

// t1Service
@Transactional
public void func() {try {testMapper.updateT1();t2Service.func();int i = 1 / 0;} catch (Exception e) {// 异常捕获了,未抛出,导致异常事务不会回滚。e.printStackTrace();}
}// t2Service
@Transactional
public void func() {testMapper.updateT2();
}

异常与rollback不匹配

@Transactional默认情况下,只会回滚RuntimeExceptionError及其子类的异常,如果是受检异常或者其他自定义的不属于其子类的异常是不会回滚事务的。

@Transactional
public void func() throws Exception {try {testMapper.updateT1();t2Service.func();int i = 1 / 0;} catch (Exception e) {// 默认情况下非运行时异常不会回滚throw new Exception();}
}

修改方式也很简单,@Transactional支持通过rollbackFor指定回滚异常类型,可以直接改成rollbackFor = Exception.class即可

// 改成rollbackFor = Exception.class即可
@Transactional(rollbackFor = Exception.class)
public void func() throws Exception {try {testMapper.updateT1();t2Service.func();int i = 1 / 0;} catch (Exception e) {throw new Exception();}
}

方法内部直接调用

func2方法是由func调用,虽然func2方法上加了@Transactional注解,但事务不会生效,testMapper.updateT2()执行的方法并不会回滚。

public void func() {testMapper.updateT1();func2();
}@Transactional
public void func2() {testMapper.updateT2();int i = 1 / 0;
}

可以修改为直接通过注入的方式调用即可。

@Service
public class T1Service {@Resourceprivate TestMapper testMapper;// 注入T1Service对象@Resourceprivate T1Service t1Service;public void func() {testMapper.updateT1();// 通过注入的方式调用t1Service.func2();}@Transactionalpublic void func2() {testMapper.updateT2();int i = 1 / 0;}
}

注意:SpringBoot 2.6.0版本开始,默认禁止循环依赖,所以如果你使用的版本是2.6.0之后的,那么启动会遇到如下报错。

As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

修改方式:在配置文件中把允许循环依赖打开即可。

spring.main.allow-circular-references=true

当然,你也可以直接使用AopContext的方式,像下面这样:

public void func() {testMapper.updateT1();T1Service t1Service = (T1Service) AopContext.currentProxy();t1Service.func2();
}
@Transactional
public void func2() {testMapper.updateT2();int i = 1 / 0;
}

在另一个线程中使用事务

Spring事务管理的方式就是通过ThreadLocal把数据库连接与当前线程绑定,如果新开启一个线程自然就不是一个数据库连接了,自然也就不是一个事务。

t2Service.func()方法操作的数据并不会被回滚。

@Transactional
public void func() {testMapper.updateT1();new Thread(() -> t2Service.func()).start();int i = 1 / 0;
}

注解作用到private级别的方法上

当你写成如下这样时,IDEA直接会给出提示Methods annotated with ‘@Transactional’ must be overridable 原因很简单,private修饰的方式,spring无法为其生成代理。

public void func() {t1Service.func2();
}
@Transactional
private void func2() {testMapper.updateT1();int i = 1 / 0;
}

final类型的方法

这个与private道理是一样的,都是影响了Spring生成代理对象,同样IDEA也会有相关提示。

数据库存储引擎不支持事务

注意,如果你使用的是MySQL数据库,那么常用的存储引擎中只有InnoDB才支持事务,像MyISAM是不支持事务的,其他存储引擎都是针对特定场景下使用的,一般也不会用到,不做讨论。
在这里插入图片描述

事务的使用建议

Spring提供了两种使用事务的方式,一种是声明式事务、一种是编程式事务,无论是哪种方式使用起来都非常简单,无需过多介绍,本节主要是针对一些使用不当的场景进行说明。

声明式事务的应用级别

在实际生产应用中是不建议在类似Service这样的class类上直接加上@Transactional注解的,因为这样会导致这个类中的所有方法在执行时都带上了事务,这样原来根本就不需要事务的方法则多了一份负担。

@Service
@Transactional // 不要加在class级别
public class DemoService { 
}

长事务、过早起开事务

简单来说,就是在整个方法的生命周期内,真正需要事务管理的方法可能只占用了20%的时间,而其他业务流程占用了80%的时间,但是由于事务是对整个方法生效,从而导致事务的整体执行时间与整个方法的执行时间一样。

@Transactional
public void func() {// 两个select花费了2秒select1();select2();// 两个save只花费了200毫秒save1();save2();
}

解决方式也很简单,把长事务拆分为短事务即可。

public void func() {select1();select2();manager.save();
}@Transactional
public void save() {save1();save2();
}

也可以直接使用编程式事务,编程式事务可以更灵活的控制事务的范围。

@Resource
private TransactionTemplate transactionTemplate;public void func() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {testMapper.updateT1();t2Service.func();int i = 1 / 0;}});
}

事务锁的问题

这和前面的事务执行时间过长是有一定关系的,过长时间的事务,更容易因为锁资源的争抢而导致服务性能下降,同时还需要注意死锁的问题。

事务的整体架构展示

前面提到过,在Spring中,我们可以直接使用TransactionTemplate或者PlatformTransactionManager来操作事务,TransactionTemplate封装了事务处理的核心流程,使用者只需直接注入即可直接使用,而PlatformTransactionManager则是更底层的接口,虽然也可以直接通过它来控制事务,但它并不是主流的使用方式,一般还是建议优先使用TransactionTemplate

接下来,本小节主要会对Spring事务的整体架构进行梳理,通过源码的分析,让使用者能够更清楚事务的内部实现原理。

类的关系图

image.png

TransactionManager接口

标识性接口

public interface TransactionManager {}

PlatformTransactionManager接口

PlatformTransactionManager接口继承了TransactionManager,定义了事务的核心方法,提交和回滚,正如前面提到的,可以直接使用它来控制事务,但并不建议这样做,正确的做法应该是继承AbstractPlatformTransactionManager类,典型的样例就是JtaTransactionManagerDataSourceTransactionManager

public interface PlatformTransactionManager extends TransactionManager {TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;void commit(TransactionStatus status) throws TransactionException;void rollback(TransactionStatus status) throws TransactionException;}

AbstractPlatformTransactionManager抽象类

这是一个典型的采用模板方法设计的抽象类,定义了关于事务的核心处理流程。

下图展示了几个关键的抽象方法,其具体的逻辑处理都在子类中。

image.png

下图是DataSourceTransactionManager子类重写的doCommitdoRollback方法。

image.png

TransactionTemplate

TransactionTemplate是具体执行事务的入口,XXXTemplate结尾的类,通常目的都是为了简化使用者处理的流程,这种命名方式也是Spring的习惯。

TransactionTemplate中定义的入口方法就是execute

image.png

入参TransactionCallback也是一个标记性接口。

@FunctionalInterface
public interface TransactionCallback<T> {@NullableT doInTransaction(TransactionStatus status);
}
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {// 方法执行的第一步就需要先确保transactionManager对象不为空,还记得transactionManager吗?就是前面分析的定义了事务处理的关键方法和核心流程的类。Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);}else {TransactionStatus status = this.transactionManager.getTransaction(this);T result;try {result = action.doInTransaction(status);}catch (RuntimeException | Error ex) {// Transactional code threw application exception -> rollbackrollbackOnException(status, ex);throw ex;}catch (Throwable ex) {// Transactional code threw unexpected exception -> rollbackrollbackOnException(status, ex);throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");}this.transactionManager.commit(status);return result;}
}

TransactionManager会作为TransactionTemplate中的一个属性来使用。

image.png

整个execute中几个关键的方法实际上都是在调用TransactionManager中提供的方法,doInTransaction则是执行业务代码的地方。

image.png

image.png

TransactionDefinition

TransactionDefinition这个接口主要用来定义事务的传播行为、隔离级别、超时时间、是否只读等属性。

image.png

总结

Spring为我们提供的TransactionTemplate类,定义了事务处理的基本流程,对外暴露一个execute方法,并通过传入一个标记性接口TransactionCallback来实现使用者的业务逻辑处理,大大简化了使用方式。

PlatformTransactionManager则更加的灵活,它的目的主要是为了能够让使用者更加方便的在事务流程前后进行业务扩展,比如它的实现类有:HibernateTransactionManager、JpaTransactionManager、JtaTransactionManager,很明显就是针对不同的ORM框架而定制的。

所以定义TransactionTemplate是为了让事务使用更加方便,定义PlatformTransactionManager是为了让扩展更加的方便。

事务的提交与回滚

最后,我们再来看看事务的提交与回滚的实现逻辑。

commit方法

commit方法是在AbstractPlatformTransactionManager类中定义的,是事务的提交的入口方法,具体的处理逻辑主要又由processCommitprocessRollback这两个方法完成。

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

processCommit

该方法执行了很多扩展方法,以及一些与事务传播类型有关的逻辑处理,最终的事务处理又由doCommit方法来实现。

private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;try {boolean unexpectedRollback = false;// 这是一个空方法,主要是交由具体的事务处理器来实现prepareForCommit(status);// 下面两个方法都是Spring留出的扩展点,通过TransactionSynchronizationManager提供的registerSynchronization方法,可以注册TransactionSynchronization实例,从而调用TransactionSynchronization提供的相关方法triggerBeforeCommit(status);triggerBeforeCompletion(status);beforeCompletionInvoked = true;// 嵌套事务的处理分支if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Releasing transaction savepoint");}unexpectedRollback = status.isGlobalRollbackOnly();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 doCommittriggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);throw ex;}catch (TransactionException ex) {// can only be caused by doCommitif (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和triggerAfterCompletion,和前面的triggerBeforeCommit、triggerBeforeCompletion两个方法一样,都是Spring留下的扩展点triggerAfterCommit(status);}finally {triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);}}finally {cleanupAfterCompletion(status);}
}

doCommit

方法执行到了这一步,实际上就是获取Connection,然后调用commit即可,这是JDBC的使用标准。

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

processRollback

接下来是回滚,与事务的提交处理其实差不多,可以自行阅读。

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {try {boolean unexpectedRollback = unexpected;try {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 {// Participating in larger transactionif (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 earlyif (!isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = false;}}}catch (RuntimeException | Error ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);throw ex;}triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);// Raise UnexpectedRollbackException if we had a global rollback-only markerif (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}}finally {cleanupAfterCompletion(status);}

相关文章:

Spring事务最佳应用指南(包含:事务传播类型、事务失效场景、使用建议、事务源码分析)

前言 本文主要介绍的是在Spring框架中有关事务的应用方式&#xff0c;以及一些生产中常见的与事务相关的问题、使用建议等。同时&#xff0c;为了让读者能够更容易理解&#xff0c;本文在讲解过程中也会通过源码以及案例等方式进行辅助说明&#xff0c;通过阅读本文不但能够解…...

Go语言的Http包及冒泡排序解读

目录标题 Http一.Get二、Post三、Http服务器 BubbleSort冒泡排序 Http 一.Get package mainimport ("fmt""io/ioutil""net/http")func main() {response, err : http.Get("http://www.baidu.com")if err ! nil {fmt.Println("Ht…...

vue二维码生成插件qrcodejs2-fix、html生成图片插件html2canvas、自定义打印内容插件print-js的使用及问题总结

一、二维码生成插件qrcodejs2-fix 1.安装命令 npm i qrcodejs2-fix --save2.页面使用 import { nextTick } from vue; import QRCode from qrcodejs2-fix; nextTick(() > {let codeView document.querySelector("#codeView");codeView.innerHTML ""…...

[SSD综述1.8] 固态存储市场发展分析与预测_固态存储技术发展方向(2022to2023)

依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< ​​​​前言 自2020年疫情爆发以来,远程办公、网上教育、流媒体等等应用引爆对消费电子及云服务的需求增长,全球数字化转型加速,带来了两年的闪存风光时…...

【Linux】多路IO复用技术③——epoll详解如何使用epoll模型实现简易的一对多服务器(附图解与代码实现)

在正式阅读本篇博客之前&#xff0c;建议大家先按顺序把下面这两篇博客看一下&#xff0c;否则直接来看这篇博客的话估计很难搞懂 多路IO复用技术①——select详解&如何使用select模型在本地主机实现简易的一对多服务器http://t.csdnimg.cn/BiBib多路IO复用技术②——poll…...

【unity实战】实现类似英雄联盟的buff系统(附项目源码)

文章目录 先来看看最终效果前言开始BUFF系统加几个BUFF测试1. 逐层消失&#xff0c;升级不重置剩余时间的BUFF2. 一次性全部消失&#xff0c;升级重置剩余时间的BUFF3. 永久BUFF&#xff0c;类似被动BUFF4. 负面BUFF&#xff0c;根据当前BUFF等级计算每秒收到伤害值&#xff0c…...

Draft-P802.11be-D3.2协议学习__$9-Frame-Format__$9.3.1.22-Trigger-frame-format

Draft-P802.11be-D3.2协议学习__$9-Frame-Format__$9.3.1.22-Trigger-frame-format 9.3.1.22.1 Genreal9.3.1.22.2 Common Info field9.3.1.22.3 Special User Info field9.3.1.22.4 HE variant User Info field9.3.1.22.5 EHT variant User Info field9.3.1.22.6 Basic Trigge…...

vSLAM中IMU预积分的作用--以惯性导航的角度分析

作为一个学过一点惯导的工程师&#xff0c;在初次接触视觉slam方向时&#xff0c;最感兴趣的就是IMU预积分了。但为什么要用这个预积分&#xff0c;在看了很多材料和书后&#xff0c;还是感觉模模糊糊&#xff0c;云里雾里。 在接触了vSLAM的更多内容后&#xff0c;站在历史研究…...

c++ libevent demo

Server::Server(const char *ip, int port) {//创建事件集合base event_base_new();struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(port);server_addr.sin_addr.s_addr in…...

51单片机锅炉监控系统仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机锅炉监控系统仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 1.主要功能&#xff1a;讲解视频2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接资料下载链接&#xff08;可点击&#xff09;&#xff1a; 51单片机锅炉监控系统仿…...

zip文件解压缩命令全

zip文件解压缩命令全 入门Zip 用法选项示例语法形式和选项基本语法压缩目录将文件添加到现有压缩文件解压缩文件将 zip 文件解压缩到指定目录列出 zip 文件中的内容将 zip 文件加密将 zip 文件解密将 zip 文件中的文件转成 UTF-8 编码Zip 压缩示例创建新的 zip 压缩文件将文件添…...

章鱼网络进展月报 | 2023.10.1-10.31

章鱼网络大事摘要 1、Louis 成功竞选 NDC 的 HoM 议席&#xff0c;将会尽最大努力推动 NEAR 变革。2、章鱼网络受邀参加在土耳其主办的 Cosmoverse2023&#xff0c;分享 Adaptive IBC 的技术架构。3、2023年10月8日章鱼日&#xff0c;是章鱼网络主网上线2周年的纪念日。 …...

数据结构 | 单链表专题【详解】

数据结构 | 单链表专题【详解】 文章目录 数据结构 | 单链表专题【详解】链表的概念及结构单链表的实现头文件打印尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除pos节点删除pos之后的节点销毁链表 顺序表遗留下来的问题 中间/头部的插⼊删除&#xff…...

前端基础之BOM和DOM

目录 一、前戏 window对象 window的子对象 navigator对象&#xff08;了解即可&#xff09; screen对象&#xff08;了解即可&#xff09; history对象&#xff08;了解即可&#xff09; location对象 弹出框 计时相关 二、DOM HTML DOM 树 查找标签 直接查找 间…...

第23期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…...

VSCode实用远程主机功能

作为嵌入式开发者&#xff0c;经常在各种系统平台或者开发工具之间切换&#xff0c;比如你的代码在Linux虚拟机上&#xff0c;如果不习惯在Linux下用IDE&#xff0c;那么我尝试将Linux的目录通过samba共享出来&#xff0c;在windows下用网络映射盘的方式映射出来&#xff0c;VS…...

并发编程: 2. 线程管控

给定一个线程&#xff0c;只要令std::thread对象与之关联&#xff0c;就能管控该线程的几乎每个细节。 2.1 线程的基本管控 2.1.1 发起线程 线程通过构建std::thread对象而启动&#xff0c;该对象指明线程要运行的任务&#xff08;函数&#xff09;。简单的任务&#xff0c;…...

使用 Python、XML 和 YAML 编写 ROS 2 Launch 文件

系列文章目录 ROS2 重要概念 ament_cmake_python 用户文档 ROS2 ament_cmake 用户文档 使用 rosdep 管理 ROS 2 依赖项 文章目录 系列文章目录前言一、Launch 文件示例1.1 Python 版本1.2 XML 版本1.3 YAML 版本 二、从命令行使用 Launch 文件1. Launching2. 设置参数3. 控制海…...

SpringCloud 微服务全栈体系(十)

第十章 RabbitMQ 一、初识 MQ 1. 同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得…...

[原创]Cadence17.4,win64系统,构建CIS库

目录 1、背景介绍 2、具体操作流程 3、遇到问题、分析鉴别问题、解决问题 4、借鉴链接并评论 1、背景介绍 CIS库&#xff0c;绘制原理图很方便&#xff0c;但是需要在Cadence软件与数据库之间建立联系&#xff0c;但是一直不成功&#xff0c;花费半天时间才搞明白如何建立关系并…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...