天安节能科技园公司做网站/cpv广告联盟
前言
本文主要介绍的是在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;
两条更新语句,分别对t1
、t2
两张表进行更新。
<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
含义:支持当前事务,如果当前不存在事务则以非事务方式执行。
t2Service
的func
方法现在没有事务了,t2Service
的func
方法配置上@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
没有事务时,把t2Service
的func
方法配置为@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
的数据会回滚,因为t2
和t1
不是一个事务。
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
应该是几种事务传播方式中最难理解的,如果不注意,NESTED
和REQUIRED
功能看起来则差不多,都可以理解为有事务则加入,没有则新启一个,但实际上NESTED
比REQUIRED
要更加灵活。
先来看第一个案例,在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
时,t1
、t2
都进行了回滚,因为是同一个事务。
但如果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的区别
接下来再来看看NESTED
和REQUIRES_NEW
的区别。
我们分别给t1Service
和t2Service
加上一段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;
所以,搞清楚实现方式以后,你就会发现如果是在主事务中抛出异常,那么子事务也会被回滚,就像下面这样:t1
、t2
都会回滚。
// 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
默认情况下,只会回滚RuntimeException
和Error
及其子类的异常,如果是受检异常或者其他自定义的不属于其子类的异常是不会回滚事务的。
@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
事务的整体架构进行梳理,通过源码的分析,让使用者能够更清楚事务的内部实现原理。
类的关系图
TransactionManager接口
标识性接口
public interface TransactionManager {}
PlatformTransactionManager接口
PlatformTransactionManager
接口继承了TransactionManager
,定义了事务的核心方法,提交和回滚,正如前面提到的,可以直接使用它来控制事务,但并不建议这样做,正确的做法应该是继承AbstractPlatformTransactionManager
类,典型的样例就是JtaTransactionManager
和DataSourceTransactionManager
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抽象类
这是一个典型的采用模板方法设计的抽象类,定义了关于事务的核心处理流程。
下图展示了几个关键的抽象方法,其具体的逻辑处理都在子类中。
下图是DataSourceTransactionManager
子类重写的doCommit
和doRollback
方法。
TransactionTemplate
TransactionTemplate
是具体执行事务的入口,XXXTemplate
结尾的类,通常目的都是为了简化使用者处理的流程,这种命名方式也是Spring
的习惯。
TransactionTemplate
中定义的入口方法就是execute
入参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
中的一个属性来使用。
整个execute
中几个关键的方法实际上都是在调用TransactionManager
中提供的方法,doInTransaction
则是执行业务代码的地方。
TransactionDefinition
TransactionDefinition
这个接口主要用来定义事务的传播行为、隔离级别、超时时间、是否只读等属性。
总结
Spring
为我们提供的TransactionTemplate
类,定义了事务处理的基本流程,对外暴露一个execute
方法,并通过传入一个标记性接口TransactionCallback
来实现使用者的业务逻辑处理,大大简化了使用方式。
而PlatformTransactionManager
则更加的灵活,它的目的主要是为了能够让使用者更加方便的在事务流程前后进行业务扩展,比如它的实现类有:HibernateTransactionManager、JpaTransactionManager、JtaTransactionManager
,很明显就是针对不同的ORM
框架而定制的。
所以定义TransactionTemplate
是为了让事务使用更加方便,定义PlatformTransactionManager
是为了让扩展更加的方便。
事务的提交与回滚
最后,我们再来看看事务的提交与回滚的实现逻辑。
commit方法
commit
方法是在AbstractPlatformTransactionManager
类中定义的,是事务的提交的入口方法,具体的处理逻辑主要又由processCommit
和processRollback
这两个方法完成。
@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框架中有关事务的应用方式,以及一些生产中常见的与事务相关的问题、使用建议等。同时,为了让读者能够更容易理解,本文在讲解过程中也会通过源码以及案例等方式进行辅助说明,通过阅读本文不但能够解…...

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

【unity实战】实现类似英雄联盟的buff系统(附项目源码)
文章目录 先来看看最终效果前言开始BUFF系统加几个BUFF测试1. 逐层消失,升级不重置剩余时间的BUFF2. 一次性全部消失,升级重置剩余时间的BUFF3. 永久BUFF,类似被动BUFF4. 负面BUFF,根据当前BUFF等级计算每秒收到伤害值,…...

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预积分的作用--以惯性导航的角度分析
作为一个学过一点惯导的工程师,在初次接触视觉slam方向时,最感兴趣的就是IMU预积分了。但为什么要用这个预积分,在看了很多材料和书后,还是感觉模模糊糊,云里雾里。 在接触了vSLAM的更多内容后,站在历史研究…...

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仿真程序原理图报告讲解视频) 1.主要功能:讲解视频2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接资料下载链接(可点击): 51单片机锅炉监控系统仿…...

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

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

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

前端基础之BOM和DOM
目录 一、前戏 window对象 window的子对象 navigator对象(了解即可) screen对象(了解即可) history对象(了解即可) location对象 弹出框 计时相关 二、DOM HTML DOM 树 查找标签 直接查找 间…...

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

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

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

使用 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. 同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得…...

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

Python 海龟绘图基础教学教案(一)
Python 海龟绘图——第 1 题 题目:绘制下面的图形 解析: 考察 turtle 基本命令,绘制直线,使用 forward,可缩写为 fd。 答案: import turtle as t t.fd(100) # 或者使用 t.forward(100) t.done() Python 海…...

JUC并发编程系列(一):Java线程
前言 JUC并发编程是Java程序猿必备的知识技能,只有深入理解并发过程中的一些原则、概念以及相应源码原理才能更好的理解软件开发的流程。在这篇文章中荔枝会梳理并发编程的基础,整理有关Java线程以及线程死锁的知识,希望能够帮助到有需要的小…...

双向链表相关代码
DLinkList.h // // DLinkList.hpp // FirstP // // Created by 赫赫 on 2023/10/31. // 双向链表相关代码:双向链表、循环双向链表#ifndef DLinkList_hpp #define DLinkList_hpp #include <stdio.h> #include <stdlib.h> #include <iostream>…...

[每周一更]-(第70期):常用的GIT操作命令
1、增删文件 # 添加当前目录的所有文件到暂存区 $ git add .# 添加指定文件到暂存区 $ git add <file1> <file2> ...# 添加指定目录到暂存区,包括其子目录 $ git add <dir># 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file…...

Leetcode-283 移动零
count记录0的个数,不为0的数取代0位置,最后把剩余位置置零 class Solution {public void moveZeroes(int[] nums) {int count 0;for(int i0;i<nums.length;i){if(nums[i]0){count;}else{nums[i-count]nums[i];}}for(int inums.length-count;i<nu…...

爱上C语言:函数递归,青蛙跳台阶图文详解
🚀 作者:阿辉不一般 🚀 你说呢:生活本来沉闷,但跑起来就有风 🚀 专栏:爱上C语言 🚀作图工具:draw.io(免费开源的作图网站) 如果觉得文章对你有帮助的话,还请…...

Pycharm 对容器中的 Python 程序断点远程调试
pycharm如何连接远程服务器的docker容器有两种方法: 第一种:pycharm通过ssh连接已在运行中的docker容器 第二种:pycharm连接docker镜像,pycharm运行代码再自动创建容器 本文是第一种方法的教程,第二种请点击以上的链接…...

自动驾驶行业观察之2023上海车展-----车企发展趋势(3)
合资\外资发展 宝马:i7、iX1新车亮相,未来将持续发力电动化、数字化(座舱) 宝马在本次车展重点展示了电动化产品,新发车型为i7 M70L、iX1、及i vision Dee概念车等车型。 • 展示重点:电动化数字化&#…...

day55【动态规划子序列】392.判断子序列 115.不同的子序列
文章目录 392.判断子序列115.不同的子序列 392.判断子序列 题目链接:力扣链接 讲解链接:代码随想录讲解链接 题意:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不…...

c语言中磁盘文件的分类
#include <stdio.h> /*磁盘文件的分类: * 一个文件通常是磁盘上一段命名的存储区计算机的存储在物理上是二进制的, * 所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储 * 从用户或者操作系统使用的角度(…...