共计 51087 个字符,预计需要花费 128 分钟才能阅读完成。
Spring 事务的集体总结
一、引言
Spring 的事务机制尽管曾经有十分多的材料介绍了,然而理论应用的时候还是经常栽坑里,置信这是大部分程序员的应用感触或者已经的应用感触,所以这篇文章将会做一个残缺的总结,用实践结合实际的代码实战的形式来弄懂 Spring 事务的机制到底应该如何应用以及外部是如何进行设计的。
在介绍注释之前,心愿这篇教程不单单是能让避开 Spring 事务的陷阱,更多的是心愿可能从 Spring 的事务设计上锤炼本人的架构思维,如果你一开始齐全不懂,能够像我一样进行一比一复刻用在 本人的开源工具或者我的项目上,不要管为什么这么设计,只管模拟再去思考,如果感觉文章写得不错,欢送点赞珍藏反对一波。
另外如果你齐全不懂 JDBC 或者数据库的话,这篇文章可能就不太适宜你看了,笔者也并不想节约读者工夫,能够看看这本老外写的书,粗略翻了一下发现还是挺适宜入门上手的,顺带磨难下英语浏览程度(切实不行还有谷歌娘兜底不是):
《jdbc》(简略粗犷的名字)
链接:https://pan.baidu.com/s/1COVi…
提取码:x5tz
– 来自百度网盘超级会员 V7 的分享
最好的材料
最好的博客永远是官网文档,倡议理解 Spring 事务事务之前多读几遍:(版本 5.3.14)
https://docs.spring.io/spring…
另外官网文档在事务这一节的最初举荐了两个扩大材料,这里也一并举荐:
无关 Spring Framework 的事务反对的更多信息,请参阅:
Spring 中的分布式事务:分布式事务极具影响力的一篇文章,也是理解分布式事务一篇必读文,带和不带 XA 的一个 JavaWorld 演示,其中 Spring 的
David Syer
将领导您理解 Spring 应用程序中分布式事务的七种模式,其中三种带 XA,其余四种不带。这里还找到一篇翻译:distributed-transactions-in-spring-with-and-without-xa.md
Java Transaction Design Strategies 是 InfoQ 提供的一本书,它对 Java 中的事务提供了节奏明快的介绍。它还蕴含无关如何通过 Spring Framework 和 EJB3 配置和应用事务的并行示例。
书籍资源链接: https://pan.baidu.com/s/1Z1Vd… 提取码: hads
二、事务介绍
首先咱们须要大抵理解什么是事务,一句话概括就是:一组操作要么全副一起执行,要么全副的不执行。
(1)事务个性(ACID)
事务通常有上面的四种个性:
- 一致性:一致性保障事务的操作前后数据都正确的。
- 原子性:事务是操作的最小单位,每一个事务要么全副胜利,要么全副失败。
- 隔离性:隔离性须要保障不同的事务之间不会相互烦扰。
- 持久性:事务提交之后数据将会永恒失效并且保留。
这四种个性尽管简略然而真要记住的确有些难,为了不便记忆咱们用一些案例进行联想记忆,也能够依照本人容易记忆的形式联想记忆:
首先,数据须要保障操作前的后果和操作后的后果统一,比方银行卡余额有 1000 元要往银行卡充值 2000,充值实现之后咱们看到的数据就是 3000 否则就还是 1000 元,所以 一致性 是首先须要保障的,而后假如此时余额还是 1000 元,此时扣除 500 元,充值 1000 元,如果胜利最初余额肯定是 1500 元,须要保障两个操作同时实现,要么同时不实现,这就是 原子性 ,原子性能够看做是保障屡次间断操作的统一。接下来是 隔离性 ,隔离性比拟好了解,你在 ATM 机器操作的时候如果进行充值,这两头的所有数据都是只有你能够见的,1000 块钱存进去看到的后果肯定是 2000 块钱而不是最初变成 500 块钱(这你相对要报警了)。最初,事务肯定是须要保障平安的,所以所有的事务只有胜利就能 永恒失效,不受忽然断电任何状况的影响,然而持久性依赖具体数据库的实现,当然这是少数数据库的次要保障。
(2)并发事务的存在的问题
并发事务存在几个常见的问题,那就是 脏读,不可反复读,幻读 , 失落回滚(第一类事务失落),失落批改(第二类事务失落),这里须要小心辨别不可反复度和幻读这两种状况。
1. 脏读:最简略的话了解就是读到了其余事务没有提交的数据,也就是读到了未提交的数据,所以这个数据不论是否正确,其执行后果可能是不正确。
2. 不可反复读:不可反复读针对以后事务读取到其余事务批改或者删除的数据,比方以后事务之前读取数据行为 20,然而后一次读取却变成了 30,数据和之前不反复了,所以就是不可反复读,然而须要留神不可反复读并不是一个非常重大的问题,这取决于数据库所以这也是为什么有不少数据库不设置默认级别为这个级别其中一个起因。
3. ** 幻读 **:幻读和不可反复读最大的区别是读到了其余事务提交的 ** 新增 ** 数据,就如同以后事务读取数据呈现了幻觉,之前读取进去的值和后一次读出来的值的内容不统一。3. ** 失落批改 **(第二类事务失落):有一种状况失落批改,失落批改指的是两个事务同时提交事务笼罩同一份数据的状况,不论事务 A 胜利还是事务 B 胜利,最初总会有一方批改的数据是生效的。3. ** 失落回滚 **(第一类事务失落):失落回滚指 A 事务提交了,然而 B 事务事务回滚了,当 A 提交胜利然而 B 因为回滚事务回滚了 A 的数据,这时候对于 A 来说事务就算是失落了。
(3)事务的隔离级别
事务的隔离级别就是来解决并发事务问题的,须要留神的是上面介绍的内容并不是所有的数据库都反对(也可能反对然而只反对一部分),如果无非凡阐明默认为 Mysql。
- READ UNCOMMITTED(读未提交):对应脏读的模式,会读到其余事务没有提交的数据,简直无数据安全和可靠性的保障。
- READ COMMITTED(读已提交):事务会读取已提交的数据,也是大多数数据库的默认隔离级别。然而读到已提交的数据存在读到其余事务提交的新增数据的状况,所以会存在可反复读的状况。
- PEPEATABLE READ(可反复读):这个级别是 MySQL 的默认隔离级别,它解决了脏读的问题,同时也保障了同一个事务屡次读取同样的记录是统一的,但这个级别还是会呈现幻读的状况。幻读是指当一个事务 A 读取某一个范畴的数据时,另一个事务 B 在这个范畴插入行,A 事务再次读取这个范畴的数据时,会产生幻行。特地阐明:InnoDB 和 XtraDB 存储引擎通过多版本并发管制(MVCC,Multiversion Concurrency Control)解决了幻读问题,它应用间隙锁(next-key locking)锁定查问波及的行和索引中的间隙,避免幻影行的插入。
- SERIALIZABLE(可串行化):这个事务是最高的隔离级别,它强制事务串行执行,防止了幻读问题。简略来说,SERIALIZABLE 会在读取的每一行数据上都加锁,所以可能会导致大量的超时和锁竞争
(4)设置和回滚到保留点
留神保留点和事务的隔离级别一样,依赖于具体的数据库是否反对。保留点应用形式是在以后事务中通过 Connection.setSavepoint
设置一个 Savepoint
对象,利用 Connection.rollback
办法进行回滚,同时承受一个 Savepoint
参数。能够了解为一个存档,存档之后,能够回滚到指定存档点,这里用一个网上的 sql 案例简略了解,上面的案例重点关注:ROLLBACK TO SAVEPOINT s1;
和 SAVEPOINT s1;
和SAVEPOINT s2;
这几条语句:
回滚事务到保留点。-- 删除表 bonus_2017。DROP TABLE IF EXISTS bonus_2017;
-- 创立表 bonus_2017。CREATE TABLE bonus_2017(staff_id INT NOT NULL, staff_name CHAR(50), job VARCHAR(30), bonus NUMBER);
-- 向表 bonus_2017 中 insert 记录 1。INSERT INTO bonus_2017(staff_id, staff_name, job, bonus) VALUES(23,'limingwang','developer',5000);
-- 提交事务。COMMIT;
-- 设置保留点 s1。SAVEPOINT s1;
-- 向表 bonus_2017 中 insert 记录 2。INSERT INTO bonus_2017(staff_id, staff_name, job, bonus) VALUES(24,'liyuyu','tester',7000);
-- 设置保留点 s2。SAVEPOINT s2;
-- 查问表 bonus_2017 的数据。SELECT * FROM bonus_2017;
STAFF_ID STAFF_NAME JOB BONUS
------------ -------------------------------------------------- ------------------------------ ----------------------------------------
23 limingwang developer 5000
24 liyuyu tester 7000
2 rows fetched.
-- 回滚到保留点 s1。ROLLBACK TO SAVEPOINT s1;
-- 查问表 bouns_2017 的数据。SELECT * FROM bonus_2017;
STAFF_ID STAFF_NAME JOB BONUS
------------ -------------------------------------------------- ------------------------------ ----------------------------------------
23 limingwang developer 5000
1 rows fetched.
三、Spring 事务介绍
(1)Spring 事务是啥?
Spring 的事务能够看做是在 spring 对于不同数据库的操作细节的一种封装和覆盖,通过三个外围的组件来实现整个事务的管制,这三个组件别离是:
- PlatformTransactionManager:(平台)事务管理器
- TransactionDefinition: 事务定义信息(事务隔离级别、流传行为、超时、只读、回滚规定)
- TransactionStatus: 事务运行状态
spring 事务在进行操作的时候通过自定义了七种不同的事务等级来实现事务提交回滚等一系列的操作,这里先卖个关子。
(2)事务设计
咱们略微扩大介绍一下下面三个组件的内容:
- PlatformTransactionManager:(平台)事务管理器,定义了事务的通用接口,最次要的实现抽象类为:AbstractPlatformTransactionManager,如后面所说,他定了事务操作的模板办法,由子类实现具体的事务细节,比方回滚,提交事务,设置参数等操作。
-
TransactionDefinition: 事务定义信息,能够看作是一个事连贯务的属性和配置,事务属性分为上面五种:
- 事务隔离级别:事务隔离几倍并不是所有的数据库都反对,并且局部数据库因为存储引擎的不同也可能不反对。
- 流传行为:流传行为有七种,默认级别为:PROPAGATION_REQUIRED。
- 事务超时:指一个事务所容许执行的最长工夫,如果超过该工夫限度但事务还没有实现,则主动回滚事务。
- 回滚规定:规定了以后的事务会在什么状况下进行回滚,默认状况是依照运行时异样进行回滚,也能够设置指定某些异样不进行回滚,然而须要留神指定异样类必须是 Throwable 的子类。
- 是否只读:事务的只读属性是指对事务性资源进行只读操作或者是读写操作
- TransactionStatus: 事务运行状态,能够看做是一个新的事务对象,此接口蕴含了上面的内容
public interface TransactionStatus{boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有复原点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已实现
}
(3)实现模式
Spring 事务有两种实现模式,对于实现模式将会在后文进行具体的解说:
- 编程式模式:硬编码,也能够称之为手动模式其实能够看作对于 jdbc 做了一层封装,个别应用 TransactionTemplate 模板对象进行事务的相干操作。
- 申明式模式:特点是应用 @Transactional 注解实现切面的事务注入。其底层应用的是 aop 和动静代理。
spring 的事务是对于 aop 和动静代理的编程思维的一个教科书实现,如果对于 spring 源代码比拟辣手的能够从 spring 的事务开始理解,如果看不懂也须要补补 aop 和动静代理的功课,上面咱们别离来看看编程式事务和申明式事务的实现形式。
(4)事务的流传级别
spring 的事务流传级别次要是上面的七种,对于事务流传的局部细节会在申明式事务的“了解事务流传级别”介绍,同时实战局部会通过一些简略的案例来看一下不同流传级别的成果,这里能够先大抵浏览一遍,对于事务流传的具体案例操作将会在实战局部进行具体的论述。
特点 | 流传级别 | 阐明 |
---|---|---|
反对以后事务的状况: | TransactionDefinition.PROPAGATION_REQUIRED | 如果以后存在事务,则退出该事务;如果以后没有事务,则创立一个新的事务。 |
反对以后事务的状况: | TransactionDefinition.PROPAGATION_SUPPORTS | 如果以后存在事务,则退出该事务;如果以后没有事务,则以非事务的形式持续运行。 |
反对以后事务的状况: | TransactionDefinition.PROPAGATION_MANDATORY | 如果以后存在事务,则退出该事务;如果以后没有事务,则抛出异样。(mandatory:强制性) |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_REQUIRES_NEW | 创立一个新的事务,如果以后存在事务,则把以后事务挂起。 |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 以非事务形式运行,如果以后存在事务,则把以后事务挂起。 |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_NEVER | 以非事务形式运行,如果以后存在事务,则抛出异样。 |
其余状况 | TransactionDefinition.PROPAGATION_NESTED | 如果以后存在事务,则创立一个事务作为以后事务的嵌套事务来运行;如果以后没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。 |
(5)Spring 事务和传统的区别
Spring 事务和传统事务区别如下:
- 咱们不须要捕捉 SQLException,Spring 会主动捕捉异样并且转为相干的 RuntimeException 并且进行回滚。
- 通过 Spring 事务管理,能够极大化的缩小配置,并且能够将事务委托为 Spring 进行治理。
- 传统事务会产生大量的侵入式代码,并且切换数据库对系统也是毁灭性打击。
(6)设计思路(不重要)
设计思路局部是集体看完了 Spring 事务之后的一些思考补充,能够跳过。
下面简略的介绍了 spring 事务的大抵特点,以及介绍了传统事务的个性以及并发事务的问题,上面来看一下 Spring 是如何设计本人的事务的,这里读者能够停留下来思考一下,如果你是事务的设计者,你会如何设计?留神这有利于你更深刻的理解和坚固对于 Spring 事务的相干常识,这里大抵说下集体的思考思路:
首先操作层面来看依据事务的个性,因为事务是多个间断的原子操作,所以必定须要 开启和提交事务 这两个步骤,而后咱们在中途必定须要对事务设置一些本人设置的属性,并且 通过这些属性管制事务是否是只读的,不容许批改,管制是否容许应用事务 等等,接着咱们须要思考不必的数据库要如何接入,也就是 数据源要如何进行治理?这时候必定会想必定分发给不同的数据源事务管理器进行管制,然而这时候又会发现无论是回滚还是提交事务这些办法都是独特的,只是实现形式不同的而已,所以这里能够应用策略模式把事务的操作分发给具体的数据源实现,然而因为有独特的办法所以须要应用 模板办法 的模式管制形象和模板办法的不同细节,最初最重要的是如何实现嵌套事务的回滚以及多线程拜访的时候源放弃线程的隔离呢?这里咱们最常想到的是ThreadLoacal,所以最初咱们从最顶层的形象来看,实现一个事务其实只须要三个操作:1. 依据配置获取事务,2. 执行操作,3. 提交或者回滚事务。
当然 Spring 的事务设计必定比这个简单多了,并且有更加欠缺的架构,然而在学习源码之前,脑子肯定要有一套本人的思路,哪怕是错的也无所谓,静下心来看看设计者是如何思考的对照本人的思路进行纠正,否则很容易会被代码细节牵着走。
四、编程式事务
编程式事务仅仅作为申明式事务的底层实现理解,当初根本不会用间接应用编程式事务,咱们须要重点理解的局部在于外围的事务组件 事务管理器。
(1)实现形式
咱们先来啰嗦一下编程式事务的实现形式,在代码中通常有上面两种实现形式:
- 第一种:应用 TransactionTemplate 的模板类进行相干操作。
- 第二种:应用 PlatformTransactionManager 管制事务的解决。
那么哪一种形式更罕用的?其实两种形式 都不举荐应用,因为申明式事务和他们相比更加不便,然而编程式事务是申明式事务的实现根底,所以咱们不得不好好聊聊他是如何实现的。
(2)配置形式
配置形式个别不怎么须要记忆,然而有时候会有奇葩的面试官会问这个货色看你有没有理论用过,其实少数状况都是不相熟,所以这里列一个大抵的模板:
<!-- 配置 c3po 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入属性值 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/xxxxx"></property>
<property name="user" value="xxx"></property>
<property name="password" value="xxxxx"></property>
</bean>
<!-- 编程式事务管理 -->
<!-- 配置事务管理器,必须是 PlatformTransactionManager 所反对的实现类 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入 dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<!-- 注入真正进行事务管理的事务管理器,name 必须为 transactionManager 否则无奈注入 -->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
</bean>
小贴士:留神和申明式的配置形式辨别。
(3)PlatformTransactionManager
集成形式
这里最快的解决形式是构建一个 Spring boot web 我的项目进行引入和间接应用,因为在 4.2 版本之后 SpringBoot 我的项目能够不加任何注解增加事务反对(默认配置),如果不晓得如何操作,首先咱们在 Maven 当中引入上面的依赖:
能够参考上面的博客疾速搭建一个 SpringBoot 我的项目:
Spring Boot 疾速搭建我的项目 – 知乎 (zhihu.com)
<!-- MYSQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
为了察看事务的操作,这里构建一张试验表:
CREATE TABLE `admin` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
疾速编写一个单元测试来验证事务是否失效:
// 单元测试
class TransactionTemplateTest(){
@Autowired
private ProgrammaticTrasaction programmaticTrasaction;
/**
* 应用 TransactionTemplate 进行事务处理
*/
@Test
public void transactionTemplateTest(){programmaticTrasaction.insert();
}
}
@Component
public class ProgrammaticTrasaction {
// 异样触发开关
private static final boolean EXCEPTION_FLAG = true;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
public void insert(){
// 1. 构建事务管理器,同时应用零碎的数据源进行设置
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(Objects.requireNonNull(jdbcTemplate.getDataSource()));
// 2. 配置事务的属性,比方事务的隔离级别,事务的超时工夫以及隔离形式等等
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
// 3. 开启事务,通过默认的事务属性配置,调用 platformTransactionManager.getTransaction 开启事务操作,此时获取到事务的状态 TransactionStatus
TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
// 4. 执行和事务无关操作,这里用新增作为解决
System.err.println("查问数据列表");
try {System.err.println(jdbcTemplate.queryForList("select * from admin"));
// 留神,jdbctemplate 把所有对于数据的改变操作对立为 update 操作,而查问则以 query 结尾
jdbcTemplate.update("insert into admin (id, username, password) values (51,' 老王 ','123')");
jdbcTemplate.update("insert into admin (id, username, password) values (21,' 老张 ','222')");
// 5. 提交事务
platformTransactionManager.commit(transaction);
// 6. 手动抛出异样进行回滚
//if(EXCEPTION_FLAG){// throw new RuntimeException("手动抛出异样");
//}
} catch (Exception e) {
// 6. 咱们须要应用事务管理器的 rollback 办法回滚整个事务
System.err.println("异样:触发事务回滚");
platformTransactionManager.rollback(transaction);
}
System.err.println("插入数据之后");
System.err.println(jdbcTemplate.queryForList("select * from admin"));
}/* 运行后果:第一次执行
查问数据列表
[{id=1, username=admin, password=123456}]
插入数据之后
[{id=1, username=admin, password=123456}, {id=21, username= 老张, password=222}, {id=51, username= 老王, password=123}]
第二次执行:查问数据列表
[{id=1, username=admin, password=123456}, {id=21, username= 老张, password=222}, {id=51, username= 老王, password=123}]
触发事务回滚
插入数据之后
[{id=1, username=admin, password=123456}, {id=21, username= 老张, password=222}, {id=51, username= 老王, password=123}]
*/
}
下面的代码看起来和咱们编写模板代码操作没什么区别,其实 spring 为咱们做好了不少的筹备和繁琐的根底工作,上面咱们对 spring 事务的外围代码剖析一波。
源码解析
上面是从网络上找到的一张对于编程式事务的接口图,比拟形象的阐明了 Spring 事务的整体设计,后续申明式事务和各种模板操作类其实都是基于上面这个构造的扩大:
PlatformTransactionManager 事务管理器
首先咱们来看下事务管理器接口的结构图,这里须要留神的是 TransactionManager 这个接口是一个标记接口,标记为 Spring 的事务管理接口,其余所有的对象要染指 spring 的事务必须实现此标记接口(当然通常是实现 PlatformTransactionManager 接口,或者扩大形象的默认事务管理器实现)。
提醒:不同 spring 版本类图可能会有区别
上面是 PlatformTransactionManager 事务管理器接口,也是 spring 事务管理机制的外围接口。
public interface PlatformTransactionManager extends TransactionManager {
/**
开启一个新的事务
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
提交一个事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
事务回滚
*/
void rollback(TransactionStatus status) throws TransactionException;
}
从类图能够看到,这里存在许多的实现类,这里咱们挑几个比拟常见的类进行介绍:
- JtaTransactionManager:jta 事务管理器,继承自一个形象的事务管理实现,如果你须要应用分布式的事务管理进行操作,能够应用这个事务管理器,当然少数状况它派不上用场。
小贴士:
JPA 是啥玩意?JPA 自身是一个标准,基于此标准实现的产品有:Hibernate, Eclipselink, Toplink, Spring Data JPA, etc 等,其中最典型的实现是 ORM 的鼻祖 Hibernate。如果想要更多理解 JPA,能够看看这篇客:什么是 JPA?Java Persistence API 简介。
JTA 又是啥玩意?留神不要和 JPA 搞混了,JTA 指的是容许应用程序执行 分布式事务管理,在两个或多个网络计算机资源上拜访并且更新数据,JDBC 驱动程序的退出 JTA 反对极大地加强了数据拜访能力,如果对于 JTA 细节和标准理解,能够看文章最结尾的博客,这里不再过多介绍。
- DataSourceTransactionManager:如果应用了数据源进行操作,比方应用了 jdbcTemplate,mybatis 这种框架的时候须要应用此事务管理器。
- CciLocalTransactionManager:CCI(Common Client Interface)是应用程序用来与连接器交互并与 EIS 通信的接口。同样还为本地事务划界提供了 API,Spring 提供 CCI 的反对次要是为了同价通用的资源和事务管理工具,然而其实用的非常少。
Spring 是如何获取事务的?
如何让 Spring 获取不同数据源的事务的呢?外围在于事务管理器的一个默认实现 AbstractPlatformTransactionManager 类,默认实现了获取事务的形象逻辑,将具体的获取逻辑散发到,上面为办法org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
办法的执行流程:
- 查看是否传递事务属性 TransactionDefinition,如果没有则应用默认配置。
- 以 DataSourceTransactionManager 数据源为例,通过
DataSourceTransactionManager#doGetTransaction()
办法,构建一个事务对象,同时设置相干的属性,通过 TransactionSynchronizationManager#getResource() 同步 获取 ConnectionHolder(连接器)对象,TransactionSynchronizationManager.getResource()
办法比拟要害,Spring 将连接器对立配置在一个 NamedThreadLocal 的全局线程共享资源当中,同时因为 key 有可能是一个动静的代理对象,所以 key 的获取会尝试解包动静代理来获取理论指标对象或者间接应用通明代理两种模式,当 NamedThreadLocal 中配置的连接器被获取到则进行返回(此时会额定查看以后线程是否还存在连接器存在于全局变量中,如果不存在则移除以后的线程资源开释内存)。 - 之后到代码逻辑较为简单,通过事务属性对象 TransactionDefinition 进行事务的流传机制判断,而后便是相熟的开启事务和执行 sql 以及返回后果等等一系列操作的模板代码,这里联合了策略和模板的设计模式,代码都比拟好懂。
- 在提交事务最初有一步比拟非凡,在事务提交之后如果发现以后的连贯是一个新的连贯,则会调用TransactionSynchronizationManager#bindResource 将以后的连接器进行绑定,这样能够保障同一线程内流传的是同一个事务。
上面是获取事务的形象办法,上面是对于获取事务办法的细节:
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 查看是否传递事务属性 TransactionDefinition,如果没有则应用默认配置。TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 由具体的数据源获取一个事务,比拟罕用的是
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
if (isExistingTransaction(transaction)) {
// 查看以后是否曾经存在事务,则依据事务的流传等级进行判断,新建事务或者抛出异样等解决
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 查看事务超时工夫是否设置
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// 如果没有发现事务,则查看是否事务流传级别异样
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {logger.debug("Creating new transaction with name [" + def.getName() + "]:" + def);
}
try {
// 开启事务
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {resume(null, suspendedResources);
throw ex;
}
}
else {
// 创立“空”事务:没有理论事务,但可能是同步的。if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated;" +
"isolation level will effectively be ignored:" + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
最初咱们把下面的形容简化为伪代码的模式进行解释:
DataSource 事务对象,代表一个 ConnectionHolder,用作 DataSourceTransactionManager 的事务对象。
小常识:如果 Springboot 没有配置数据源,默认会应用一个叫做 HikariPool 的数据源,这个配置 在 2.0 之后为默认值。
// 应用 threadlocal 绑定一个全局共享的全局变量
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
DataSource dataSource = platformTransactionManager.getDataSource();
// 上面为事务管理器获取细节 ====>
// 从全局共享变量获取以后线程对应的资源池
Map map = resources.get();
// actualKey => 为理论的对象(aop 为理论对象)ConnectionHolder localConnectionHolder = resources.get(actualKey, map.get(actualKey));
if(map.isEmpty()){
// 额定查看以后线程是否还存在连接器存在于全局变量中,如果不存在则移除以后的线程资源开释,防止被开释的线程对象沉积在共享资源
resources.remove();}
// 设置手动提交事务
localConnectionHolder.setAutoCommit(false);
map.put(datasource,connection);
resources.set(map);
// <==========
再一次强调整个 spring 事务的外围局部是 AbstractPlatformTransactionManager,它定义了 spring 获取事务的绝大多数的专用逻辑,而具体对于获取,提交,回滚等等细节则须要由子类的具体事务管理器进行实现。
最初,编程式事务在以前应用还是非常常见的(因为也没有别的工具),只不过 spring 推出注解式的申明式事务太好用了才逐步淡出了视线,然而学习这些内容有助于更好的了解 spring 是如何实现注解式事务的,因为外围实现还是通过事务管理器来实现。
小贴士:看到这里倡议劳动一下整顿一下思路,进来倒杯水回来缓缓接着看。
(4)TransactionTemplate
当初咱们来看下第二种形式 TransactionTemplate,第二种形式其实是对于纯手动模式的进一步封装,咱们能够发现事务管理器的模板代码其实能够省出开启事务和提交事务和回滚的步骤,这些步骤仍然会呈现大量的模板代码,并且客户端也不喜爱手动开启和敞开事务,Spring 针对此进行改良呈现了TransactionTemplate 事务的模板办法类,上面咱们依据一个理论的运行代码来解说一下它的用法:
应用形式
public void update(){
// 1.2 不须要应用事务管理器,而是间接在 transactionTemplate 外部集成
// 3. 区别点,这里应用了 transactionTemplate 进行事务的开启和管制,上面介绍他的两种次要应用形式
// 3.1 transactionTemplate.executeWithoutResult(Consumer<TransactionStatus> action):// 没有返回值的,需传递一个 Consumer 对象,在 accept 办法中做业务操作。(相似切面)// 3.2 <T> T execute(TransactionCallback<T> action):有返回值的,须要传递一个 TransactionCallback 对象,在 doInTransaction 办法中做业务操作
// 调用 execute 办法或者 executeWithoutResult 办法执行结束之后,事务管理器会主动提交事务或者回滚事务。// 事务在什么状况下会进行回滚?须要满足上面的条件://(1)transactionStatus.setRollbackOnly(); 将事务状态标注为回滚状态
//(2)execute 办法或者 executeWithoutResult 办法外部抛出异样
// 什么时候事务会提交?// 办法没有异样 && 未调用过 transactionStatus.setRollbackOnly();
System.err.println("事务处理前");
System.err.println(jdbcTemplate.queryForList("select * from admin"));
// 第一种应用形式
transactionTemplate.executeWithoutResult(transactionStatus -> {jdbcTemplate.update("update admin set username ='test2'where id ='51'");
jdbcTemplate.update("update admin set username ='test2',password='1111'where id ='21'");
});
// 第二种应用形式
DefaultTransactionStatus execute = (DefaultTransactionStatus) transactionTemplate.execute((TransactionCallback<Object>) status -> {jdbcTemplate.update("update admin set username ='test2'where id ='51'");
jdbcTemplate.update("update admin set username ='test2',password='1111'where id ='21'");
return status;
});
System.err.println("事务处理后");
System.err.println(jdbcTemplate.queryForList("select * from admin"));
}/* 运行后果:事务处理前
[{id=1, username=admin, password=123456}, {id=21, username= 老张, password=222}, {id=51, username= 老王, password=123}]
事务处理后
[{id=1, username=admin, password=123456}, {id=21, username=test2, password=1111}, {id=51, username=test2, password=123}]
*/
能够看到,应用TransactionTemplate 的形式缩减了很多代码,咱们不须要关怀事务开启和提交的细节,只须要关怀事务的解决操作即可。
源码解析
上面依照同样源码来解释一下这个操作的大抵解决流程,这里可能会感觉我擦这么神奇,不必事务管理器,也不须要设置属性了,那要怎么设置属性?其实只不过是把所有的工作交给TransactionTemplate 去做了,为了验证咱们能够比照DefaultTransactionDefinition 和TransactionTemplate 中的办法,能够发现他们的办法基本一致。(其实TransactionTemplate 就是继承了DefaultTransactionDefinition)。少数状况下咱们通过 @Configuration 注入 Bean 的形式,为全局的 TransactionTemplate 设置一些默认配置。
其实从集体角度来看这里应用继承的形式实现 spring 相干的事务配置并不是很好,而是应用一个配置结构器实现配置的直达或者应用组合 TransactionDefinition 的形式比拟失当。
DefaultTransactionDefinition:
TransactionTemplate:
上面咱们再来看下它的类图,能够看到他在扩大事务属性类的根底上实现了新的接口:TransactionOpration,这也是一个要害接口,另外TransactionTemplate 能够进行事务的各种操作的起因是它外部组合了 PlatformTransactionManager 的对象,所以所有的事务管理实际上还是通过 PlatformTransactionManager 对象进行治理的,换汤不换药:
上面是 TransactionOperations 的办法签名,留神 Spring5 对于这个接口做过改变和扩大,旧版本的包可能内容不同:
class TransactionOperations{
/**
带返回值的办法
*/
@Nullable
<T> T execute(TransactionCallback<T> action) throws TransactionException;
/**
jdk8 的接口默认办法, 不带返回值的应用形式
@since:5.2
*/
default void executeWithoutResult(Consumer<TransactionStatus> action) throws TransactionException {
execute(status -> {action.accept(status);
return null;
});
}
/**
返回 TransactionOperations 接口的实现,该接口在没有理论事务的状况下执行给定的 TransactionCallback。通常在测试的时候应用:该行为相当于在没有理论事务 (PROPAGATION_SUPPORTS) 和没有同步 (SYNCHRONIZATION_NEVER) 的状况下应用事务管理器运行。对于具备理论事务处理的 TransactionOperations 实现,将 TransactionTemplate 与适当的 PlatformTransactionManager 一起应用。@since:5.2
@see:TransactionDefinition.PROPAGATION_SUPPORTS,
AbstractPlatformTransactionManager.SYNCHRONIZATION_NEVER,
TransactionTemplate
*/
static TransactionOperations withoutTransaction() {return WithoutTransactionOperations.INSTANCE;}
}
再来看一下TransactionTemplate,代码绝对简略,置信读者只有理解原始的事务操作模板办法就可以看懂:
/**
阐明:在事务中执行给定回调对象指定的操作。容许返回在事务中创立的后果对象,即域对象或域对象的汇合。回调抛出的 RuntimeException 被视为强制回滚的异样。这样的异样会流传到模板的调用者。参数:action - 指定事务操作的回调对象
返回:回调返回的后果对象,如果没有,则为 null
抛出:TransactionException - 在初始化、回滚或零碎谬误的状况下
RuntimeException - 由 TransactionCallback 抛出
*/
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
// 查看以后是否存在事务管理器(不容许为空)Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
// 如果以后事务管理器反对主动回调解决,则应用事务管理器主动回调办法的实现,CallbackPreferringPlatformTransactionManager 类扩大自 PlatformTransactionManager,公开了在事务中执行给定回调的办法。if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
// 如果不反对主动回调,则转入到传统的解决模式,这部分代码和原始的事务管理操作相似
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
// 外围局部,应用 action 的函数式调用实现外部的事务处理
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// 如何呈现 RuntimeException 或者 谬误 回滚
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// 异样则依据事务级别进行回滚
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
// 如果没有出现异常,则失常提交
this.transactionManager.commit(status);
// 返回事务执行的后果
return result;
}
}
理解带返回值 execute 办法之后,咱们来理解一下不带返回值的办法executeWithoutResult()
,它的实现代码就更为简洁了,从最新版本的代码能够看到其实后续的版本有做函数式表达式重构,所以如果 jdk 版本比拟低可能呈现不兼容的状况:
/**
在事务中执行给定 Runnable 指定的操作。如果须要从回调中返回一个对象或从回调中拜访 TransactionStatus,能够改用 execute(TransactionCallback)。这样的变体相似于应用 TransactionCallbackWithoutResult 但具备针对常见状况的简化签名 - 并且能够不便地与 Java 8 lambda 表达式一起应用。参数:action - 指定事务操作的 Runnable
抛出:TransactionException - 在初始化、回滚或零碎谬误的状况下
RuntimeException - 如果由 Runnable 抛出
*/
default void executeWithoutResult(Consumer<TransactionStatus> action) throws TransactionException {
execute(status -> {action.accept(status);
return null;
});
}
(5)小结
最初咱们来小结一波下面的内容,这一节开始咱们理解了传统编程式事务的操作,能够看到尽管 Spring 曾经封装开启和敞开事务的细节,也封装了对于 JDBC 操作的模板代码,然而咱们如果应用编程式事务还是会产生大量的侵入式代码,这也不合乎繁多职责的准则。
编程式事务实现的几个特点:
- 事务的流传机制基于
AbstractPlatformTransactionManager#getTransaction()
办法,通过不同的事务流传等级设置进行事务的治理。 - 为了保障同一个线程应用的连贯资源的一致性,Spring 应用了
ThreadLocal
作为全局共享变量的存储,实现线程之间的事务隔离,同时避免并发事务的问题。 - TransactionTemplate 能够看作是对于
PlatformTransactionManager
事务管理器的二次封装并且扩大了回调式的事务操作。
再次强调破费大量篇幅介绍并不是说举荐大家应用,而是应用编程式事务更有助于咱们理解申明式事务的应用,上面咱们来理解一下申明式事务的应用。
五、申明式事务
申明式事务的应用就非常简略易懂了,就是咱们相熟的 @Transactional
注解,做到了不侵入的增加和去除事务管理反对,申明式事务的实现应用了 AOP,实质其实就是在执行被注解的事务办法前进行拦挡操作,申明式事务有如下的优缺点:
长处:简略疾速注入事务,无需改变任何业务代码。
毛病:和 spring aop 的毛病统一,只能针对办法级别进行事务管制,并且只能通过 Spring 代理对象能力应用申明式事务,如果本人 new 对象接入事务这样做是不会失效的(切记),具备肯定的局限性。
(1)如何应用?
不同的我的项目配置形式不同,须要针对具体的我的项目环境应用,目前支流是应用微服务,这里其实更加举荐应用 @Bean 类配置的形式注入事务管理的相干性能,比方配置事务管理器。如果你是 SpringBoot 并且版本比拟新,那就非常简单了,间接加注解应用即可。
小贴士:如果你应用的是 springboot2.0 之后的我的项目,并且如果在依赖外面有 spring-data-* 或者 spring-tx事务无关的依赖,则默认会开启申明式事务。
@Override
@Transactional(rollbackFor = Exception.class)
public void testAdd() {//dosomething}
然而如果 Spring Boot 的版本比拟低或者一些比拟旧的我的项目,则须要手动开启一下:
@Configuration
@EnableTransactionManagement
public class MySpringConfig {
@Bean
public PlatformTransactionManager txManager() {
// dosomething 能够本人配置事一些事务管理器的配置。return yourTxManager;
}
}
顺带一提,如果咱们想要在注解的办法外面应用编程式的 TransactionAspectSupport 类外面的办法手动回滚事务,能够应用上面的办法进行回滚(然而倡议不要这么应用):
public void createCourseDefaultRatingProgramatic(Course course) {
try {courseDao.create(course);
} catch (Exception e) {
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
@Transactional 注解介绍
咱们来看一下对于 @Tralsactional
领有哪些配置:
- value:别名,指定应用的事务管理器的 Bean
- transactionManager:指定事务的限定符值。可用于确定指标事务管理器,匹配特定 TransactionManager bean 定义的限定符值(或 bean 名称)。
- propagation:用于指定事务的流传级别,外围和罕用属性
- isolation:事务的隔离级别,如果没有非凡指定,则默认依照数据库的级别
- timeout:代表以后事务的超时工夫(以秒为单位),默认为底层事务零碎的默认超时工夫(留神代码给了 1 秒的默认值)。专为与
Propagation.REQUIRED
或Propagation.REQUIRES_NEW
一起应用而设计,须要留神的是 仅实用于新启动的事务。 - readOnly:默认为 false,如果设置为 true,则只运行执行非 update 操作,如果碰到 Update 的操作反而会抛出异样。此配置形式有利于进步事务执行效率,如果不须要 update 相干事务,能够将此设置为 true。
- rollbackFor:依据指定的 Class 异样类进行回滚,如果没有设置,默认为运行时异样,然而须要留神必须是 able 的子类。
- rollbackForClassName:必须是 able 的子类齐全限定类名的子字符串,目前不反对通配符。例如,
ServletException
的值将匹配javax.servlet.ServletException
及其子类。 - noRollbackFor:和 rollbackFor 刚好相同。
- noRollbackForClassName:和 rollbackForClassName 刚好相同。
如果在多个中央应用了雷同的注解,然而咱们又不想总是复制粘贴大量的反复注解配置的时候,有一种形式是应用自定义注解的形式,这里简略理解即可。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {}
监听器绑定事件
监听器事务绑定事件须要 Spring4.2 以上才反对,须要Spring4.2 以上才反对,须要Spring4.2 以上才反对,很重要,所以特意强调三遍。通常状况下应用@EventListener
注解注册惯例事件侦听器。如果须要将其绑定到事务也能够用@TransactionalEventListener
,这样能够实现监听器的事务操作。
上面是官网提供的案例:
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {// ...}
}
@TransactionalEventListener 这个注解有个比拟非凡的相熟就是 phase
这个属性:
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
这个属性反对事务绑定到指定的阶段,蕴含上面几个级别:
- BEFORE_COMMIT
- AFTER_COMMIT(默认)
- AFTER_ROLLBACK
- AFTER_COMPLETION
监听器应用事务的场景集体目前没有碰到,所以这里仅仅依据官网文档简略翻译介绍了一番。
JTA 的集成
如果须要集成 JTA,那么配置文件不能应用某一特定的资源,而是须要配合 JNDI 同时配合 JtaTransactionManager 事务管理器进行全局的事务管理,上面咱们来看一下配置的形式。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
https://www.springframework.org/schema/jee/spring-jee.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<!-- other <bean/> definitions here -->
</beans>
和 JPA/Hibernate 的集成
留神不要眼花了,上节说的是JTA,这一节讲述的是 ORM 相干的 JPA,对于他们的概念这里不再赘述,如果你的数据库应用的是 JPA,应用申明式事务能够防止一个比拟外围的问题,上面是代码的案例:
public class UserService {
@Autowired
private SessionFactory sessionFactory; // (1)
public void registerUser(User user) {
// 开启连贯
Session session = sessionFactory.openSession(); // (2)
// 开启一个 JPA 的事务
session.beginTransaction();
// 执行一个 insert 操作
session.save(user);
// 提交事务
session.getTransaction().commit();
// 敞开连贯
session.close();}
}
这样的代码会呈现上面两个重大的问题:
- Hibernate 不晓得 Spring 的
@Transactional
正文。 - Spring 的
@Transactional
对 Hibernate 的事务无所不知。
如果咱们应用申明式事务就不会呈现下面的状况,Spring 会在事务启动的时候,会主动切换到 HibernateTransactionManager 或 JpaTransactionManager(如果通过 JPA 应用 Hibernate),HibernateTransactionManager 会确保这两种会话模式合并为同一个,这里也体现了一个十分常见的适配器模式的思路。
响应式事务
响应式事务目前仍然属于一个比拟新鲜的货色,也是 Spring 的将来主攻方向,极限编程的思维比拟有意思,然而从目前环境来看应用频率比拟低,所以此文就不再进行扩大讲述。
还有更多的集成和应用形式能够参阅官网的文档,文章篇幅无限不能八面玲珑。
(2)配置形式
申明式事务的配置形式有四种,其中最常应用的是注解的形式进行事务处理。
- TransactionInterceptor:应用 TransactionInterceptor 拦截器的形式,这种形式尽管不举荐应用,然而对于了解 spring 事务是有肯定帮忙的,感兴趣能够浏览一下这个类。
- 基于 AspectJ 的 XML 形式(\<tx\> 和 \<aop\> 标签):不须要改变类,在 XML 文件中配置好即可,比方上面提供了一个事务的配置模板,同样是基于切面 aop 进行拦挡减少事务的。
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 告诉 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 流传行为 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* service.*.*(..))" />
</aop:config>
- 基于事务代理工厂的 Bean 进行配置:比方上面配置了一个代理工厂的 Bean – TransactionProxyFactoryBean,这种形式在以前是 Spring 举荐的形式,配置形式也比拟古老,并且早在 Spring2.0 就不举荐应用了。
.....
<!-- 事务管理器 -->
<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务代理工厂 -->
<!-- 生成事务代理对象 -->
<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="myTracnsactionManager"></property>
<property name="target" ref="buyStockService"></property>
<property name="transactionAttributes">
<props>
<!-- 次要 key 是办法
ISOLATION_DEFAULT 事务的隔离级别
PROPAGATION_REQUIRED 流传行为
-->
<prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
<!-- -Exception 示意产生指定异样回滚,+Exception 示意产生指定异样提交 -->
<prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
</props>
</property>
</bean>
.....
- 最初就是常见 应用 @Transactional 注解的形式进行配置 ,如果 SpringBoot 能够配合
@EnableTransactionManagement
间接引入事务,@Transactional 能够配置在某个类上,也能够配置在办法上,然而同样依赖 AOP 同时只反对办法级的事务管制而不反对代码块级的事务管制。
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
(3)了解事务流传级别
在上文咱们提到了通过事务属性对象 TransactionDefinition 进行事务的流传机制判断,这里咱们接着来看一下事务的流传机制的类型,事务的流传级别在前文曾经提到过了这里咱们再回顾一下:
特点 | 流传级别 | 阐明 |
---|---|---|
反对以后事务的状况: | TransactionDefinition.PROPAGATION_REQUIRED | 如果以后存在事务,则退出该事务;如果以后没有事务,则创立一个新的事务。 |
反对以后事务的状况: | TransactionDefinition.PROPAGATION_SUPPORTS | 如果以后存在事务,则退出该事务;如果以后没有事务,则以非事务的形式持续运行。 |
反对以后事务的状况: | TransactionDefinition.PROPAGATION_MANDATORY | 如果以后存在事务,则退出该事务;如果以后没有事务,则抛出异样。(mandatory:强制性) |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_REQUIRES_NEW | 创立一个新的事务,如果以后存在事务,则把以后事务挂起。 |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 以非事务形式运行,如果以后存在事务,则把以后事务挂起。 |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_NEVER | 以非事务形式运行,如果以后存在事务,则抛出异样。 |
其余状况 | TransactionDefinition.PROPAGATION_NESTED | 如果以后存在事务,则创立一个事务作为以后事务的嵌套事务来运行;如果以后没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。 |
了解 PROPAGATION_REQUIRED
:
PROPAGATION_REQUIRED 如果不存在事务,就新建事务,如果呈现嵌套的事务就会参加外层的事务而不会再新建。这是同一线程内公共调用的非常不便的默认设置(例如委托给多个存储库办法的服务外观,其中所有底层资源都必须参加服务级别事务),同时根本能凑合绝大多数的业务零碎。
探讨:这里讨论一下 Spring 官网文档和源码阐明的“抵触”,默认状况下,如果呈现嵌套事务,会默认采纳已有事务,这样针对外部新建的事务会默认 疏忽事务隔离级别、超时值或只读标记(如果有)。如果心愿在内部事务采纳不同隔离级别时候被内部事务回绝并且抛出异样,能够思考将事务管理器上的 validateExistingTransactions 标记切换为 true。
其实这种说法和源代码来看其实不是特地吻合,实际上如果设置 validateExistingTransactions 为 false,会发现事务将继续执行,即便隔离级别与申请的不同,会依照内部事务的优先级执行,这样其实就导致了事务配置只看一份文件的状况,具体能够参考上面这一段代码:
if (isValidateExistingTransaction()) {if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {I} // 如果设置了只读然而以后事务没有只读,则抛出异样 if (!definition.isReadOnly()) {if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] is not marked as read-only but existing transaction is"); } } } // 没有则同步构建一个空事务
最初再补一张官网的图:
了解 PROPAGATION_REQUIRES_NEW
:
PROPAGATION_REQUIRES_NEW
与 相比 PROPAGATION_REQUIRED
, 始终为每个受影响的事务范畴应用独立的事务 ,从不参加内部范畴的现有事务。每一个事务资源都是独立的,因而能够独立提交或回滚,内部事务同样不受内部事务回滚状态的影响,内部事务的锁在实现后立刻开释。这样一个独立的内部事务也能够申明本人的隔离级别、超时和只读设置, 而不继承内部事务的个性。
了解 PROPAGATION_NESTED
:
PROPAGATION_NESTED
应用了保留点的形式。嵌套事务意味着能够实现局部事务的回滚,然而嵌套的内部事务不会影响内部事务的执行。留神这个设置仅实用于 JDBC 资源事务,源码外面也有解释,能够参阅 Spring 的DataSourceTransactionManager
。
(3)源码解析
申明式事务要比编程式事务简单不少,这一部分的代码不会深刻解说 AOP 的局部,同时因为应用了 SpringBoot 主动注入的个性这里也尽量不适度深刻讲述,上面咱们从两个局部来剖析申明式式的外部操作流程:
- 主动配置注入
- 注解拦挡解析
主动配置注入
首先来啃啃第一块大骨头。咱们先从一个 SpringApplication 的注解开始开始,上文提到过 SpringBoot 新版本中如果碰到事务的相干依赖,会主动接入 Spring 的事务管制,咱们能够通过注解 @EnableTransaction 进行排查,发现外部存在一个 @EnableAutoConfiguration,Spring 启动的时候,会扫描源代码包根门路上面一个叫做 spring.factories
的文件,在大概 127 行地位有一个 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
的配置,在这个配置上面中有一个 TransactionAutoConfiguration
的配置,当容器启动的时候会主动依据配置扫描相干的包载入相干的 bean。最初从事务主动注入的注解内,能够从上面的截图看到一些相熟的影子,这里不再反复解释。
接着咱们再回过头看一下 @EnableTransaction 注解外部的内容,这里应用了 @Import 注入了 TransactionManagementConfigurationSelector,这个类的作用是依据导入 @Configuration 类上的 EnableTransactionManagement.mode() 的值抉择应应用 AbstractTransactionManagementConfiguration 的哪个实现。
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement
咱们能够从上面的代码看到,这里提供了两种代理形式,一种是依据 JDK 的代理模式,另一种是依据 AspectJ 的代理默认,Spring 在默认状况下会优先选择 JDK 的代理,如果没有才会抉择 AspectJ 的代理(总感觉 Spring 作者对于 Aop 总有一种遗憾和执念),如果读者想晓得在哪里配置的,能够看看 org.springframework.scheduling.annotation.EnableAsync#mode
中存在一个默认的 JDK 代理配置,在上面的代码中能够到走 JDK 的代理注入了两个类 AutoProxyRegistrar.class
和ProxyTransactionManagementConfiguration.class
。
AdviceMode mode() default AdviceMode.PROXY;
----- 分割线 ----
/**
依据抉择的代理模式生成不同的代理器
*/
@Override
protected String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {
// 默认都会走 JDK 的代理
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
// 如果抉择 ApectJ,能够参考上面的代码
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
AutoProxyRegistrar.class
:
这个类直译就是主动代理注册器,它是一个后置处理器,负责给容器中注册一个 InfrastructureAdvisorAutoProxyCreator,InfrastructureAdvisorAutoProxyCreator,利用后置处理器机制在对象创立当前,对对象进行包装,返回一个代理对象(增强器),代理对象执行办法,利用拦截器链进行调用,上面是对于注册器的局部代码:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
for (String annType : annTypes) {
// 解析和获取注解信息
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (candidate == null) {continue;}
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
// 校验是否设置开启代理,如果配置则查看是否为 JDK 代理
candidateFound = true;
if (mode == AdviceMode.PROXY) {
// 外围代码,如果有必要就创立一个主动代理创立器
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
// 强制将主动代理创立器绑定到用户代理
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
//......
}
接着进入办法 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
的外部实现 org.springframework.aop.config.AopConfigUtils#registerOrEscalateApcAsRequired
办法,AopConfigUtils 这个工具类为 AOP 动静代理类创立器的次要负责工具:
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
// 断言是否存在 Bean,BeanDefinitionRegistry 就是 Spring 容器中 Bean 的理论存在模式
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 注册器先判断是否有注入 org.springframework.aop.config.internalAutoProxyCreator
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
// 这里依据优先级获取,第一次进来的时候是必定没有注入的
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
// 能够看作是将 InfrastructureAdvisorAutoProxyCreator 包装成 RootBeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 外围局部:注入 bean 到 ioc 到容器当中,名字就是 org.springframework.aop.config.internalAutoProxyCreator
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
咱们晓得了 InfrastructureAdvisorAutoProxyCreator 这个类是如何集成到 IOC 容器之后,发现这个类属于一个上司的实现子类,咱们通过继承链向上找到父类:AbstractAutoProxyCreator,这个类名字翻译过去是 形象主动代理创立器,他的作用是如果 bean 被子类标识为代理,则应用配置的拦截器创立代理,这里能够看作代理的生产者和执行者之间的解耦,外围代码如下局部:
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 是否必要进行加强包装解决
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
外部的代码逻辑就不过多开展了,总结 InfrastructureAdvisorAutoProxyCreator 作用就是:利用后置处理器机制在对象创立当前,对对象进行包装,返回一个代理对象(增强器),代理对象执行办法,最初利用拦截器链进行调用。
Tips:这部分代码须要对于 Spring IOC 和 AOP 的实现底层把握比拟牢固能力彻底了解,倡议浏览卡死之后不要死犟,能够补补其余的模块实现之后再来啃下事务是如何集成到 IOC 和 AOP 的。
注解拦挡解析
咱们大抵理解了主动配置主动配置是如何注入到 Spring IOC 之后,咱们接着来看下 Spring 是如何实现事务的注解拦挡解析的,也就是 @Transactional 注解的解析过程,上文说过,注解拦截器调用这里不再啰嗦,这里回到前文的对象 ProxyTransactionManagementConfiguration
也就是代理配置管理器开始,这里读者可能有疑难这个类那里忽然冒出来的,这里给个提醒:
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
上面截取了局部代码:
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
// 向切面中注入注解解析器, 专门来解析事务注解的
advisor.setTransactionAttributeSource(transactionAttributeSource);
// 向切面中注入事务的拦截器, 专门来拦挡办法, 包含事务的提交以及回滚操作
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
接着咱们看一下被注入的属性:TransactionAttributeSource
,这个类用于解决注解的属性和相干内容,也是注解解析器,这个类领有很多的实现子类,因为是注解解析器,这里咱们间接看:
局部代码如下:
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
if (jta12Present || ejb3Present) {this.annotationParsers = new LinkedHashSet<>(4);
// spring 注解解析器
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (jta12Present) {
// jta 解析起
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (ejb3Present) {
// ejb3 的解析器
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
else {
// 应用 spring 注解解析器
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
// 调用 org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation 办法,外部局部代码如下:public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
}
下面的代码比拟好了解,就是对于注解的外部进行解析和封装。接着咱们看一下是拦截器 TransactionAspectSupport,这个拦截器实现了 MethodInterceptor 接口,标记为他是一个办法的拦截器,外围办法是外部的 invoke()办法。
外部的 invoke()办法内容如下:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {// 计算出指标类:可能是{@code null}。TransactionAttributeSource 应该传递指标类以及办法,这可能来自接口。Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 适配办法执行
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
接着进入外围的适配办法invokeWithinTransaction
,代码比拟多,这里截取局部代码介绍:
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 如果事务属性为空,则该办法是非事务性的(就是非事务形式执行)TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
// 如果以后的注册器是响应式的,则依照响应式的形式解决
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {// .......}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 应用 getTransaction 和 commit/rollback 调用进行事务划分。TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 这是盘绕告诉,调用拦挡链中的下一个拦截器。理论会调用指标对象。retVal = invocation.proceedWithInvocation();}
catch (Throwable ex) {
// 指标办法调用失败
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 事务的后续清理操作
cleanupTransactionInfo(txInfo);
}
//
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// 如果合乎回滚规定则进行回滚
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 未出现异常, 也获取事务管理器则进行事务的提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
// .......
六、Spring 事务实战
(1)介绍
为了避免读者误会,这里提前介绍一下本次试验操作的 Spring 版本和数据库版本:
- spring-boot:5.2.8
- mysql:5.7
上面咱们进入理论局部,实战局部次要针对事务比拟常见的应用:
- 事务不失效的一些“坑”,以及如何躲避或者如何解决。
- 不同事务的流传机制在理论代码中的成果。
- 大事务的问题,以及相干的解决办法。
(2)事务不失效的一些坑
首先来看一下事务不失效的一些坑,同样咱们一开始须要构建一张表作为测试:
CREATE TABLE `t_trans_test` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`amount` decimal(16,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
这里构建两条数据,别离示意两个人的余额:
INSERT INTO `transaction_tb`.`t_trans_test` (`id`, `name`, `amount`) VALUES (1, '用户 A', 1000);
INSERT INTO `transaction_tb`.`t_trans_test` (`id`, `name`, `amount`) VALUES (2, '用户 B', 500);
1. 非事务办法调用事务办法
如果在一个非事务的办法外部调用一个事务的办法,无论是否加注解,会发现抛出异样的时候会事务不回滚的状况,也是最常见的状况,如下代码所形容的,假如咱们用 A 给 B 转钱,A-100 和 B +100,如果在 B 中退出事务注解,然而 A 没有加,则会发现 A 的操作是没有回滚的:
提醒:这样的操作仅作演示,工作中扣款和充值相对不是这样操作的。
// 随机异样触发 flag
private static final boolean EXCEPTIN_FLAG = true;
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 事务生效的第一种状况:非事务办法调用事务办法
*/
// 放开上面的注解即失常
// @Transactional(rollbackFor = Exception.class)
public void nonTransactionUseTrasaction(){System.out.println("发动转账");
jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
innerMethod();}
@Transactional(rollbackFor = Exception.class)
public void innerMethod(){System.out.println("承受转账");
if(EXCEPTIN_FLAG){throw new RuntimeException("转账失败");
}
jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}/* 运行后果:=========== 运行前数据筹备重置数据 ========
发动转账
承受转账
====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=900}
{id=2, name= 用户 B, amount=500}
*/
解决形式:
解决形式有两种,一种是咱们在事务 A 的下面减少一个回滚的注解,这个无需过多解释,然而如果有时候咱们想让事务在一个外部的办法外面让事务的范畴锁定在想要的办法,最常见的一种形式是“注入本人”,还有一种形式是容许获取 Spring 的代理对象,并且通过获取 Spring 的代理对象实现操作,在 spring5.0 之后的版本又减少了新的获取形式,比方 spring-test 有 AopTestUtils 来获取,上面咱们汇总一下这些办法,如果读者感兴趣能够理论验证一遍:
- 注入“本人”。
- 应用 AopContext.currentProxy()。
- 注入 ApplicationContext,获取代理对象。
// @Transactional(rollbackFor = Exception.class)
public void nonTransactionUseTrasaction(){System.out.println("发动转账");
// jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
// 常见形式:注入本人
// transactionTestService.innerMethod();
// 应用代理对象形式 1:AopContext.currentProxy()
// TransactionTestService transactionTestService = (TransactionTestService) AopContext.currentProxy();
// transactionTestService.innerMethod();
// 第三种办法,注入 ApplicationContext
// TransactionTestService transactionTestService = (TransactionTestService) applicationContext.getBean("transactionTestService");
// transactionTestService.innerMethod();
// 补充 - 获取 Class: AopUtils.getTargetClass(yourObjectInstance);
// AopUtils.getTargetClass(yourServiceClass);
}
@Transactional(rollbackFor = Exception.class)
protected void innerMethod(){jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
System.out.println("承受转账");
if(EXCEPTIN_FLAG){throw new RuntimeException("转账失败");
}
jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}
还有更多办法欢送探讨。
2. 抛出不被回滚的异样
这也是十分常见的状况,所以很多时候倡议应用注解就加上一个 rollBackFor,哪怕应用默认的规定也倡议标记一下,上面的办法尽管抛出异样,然而能够发现最终数据是没有回滚的 s:
/**
* 抛出不被解决的异样
* @throws SQLException
*/
@Transactional(rollbackFor = RuntimeException.class)
public void throwErrorException() throws SQLException {jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
System.out.println("承受转账");
if(EXCEPTIN_FLAG){throw new SQLException("转账失败");
}
jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}/* 运行后果:========== 运行前数据筹备重置数据 ========
没有抛出反对回滚的异样测试
承受转账
====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=900}
{id=2, name= 用户 B, amount=500}
*/
3. “ 吞异样 ”
吞异样也是常见的,很多时候会发现遗记抛出异样,当然理论状况没有那么显著,更多是在捕捉异样之后没有抛出:
/**
* 不抛出异样,导致不回滚
*/
@Transactional(rollbackFor = Exception.class)
public void nonThrowException(){System.out.println("发动转账");
jdbcTemplate.update("update t_trans_test set amount=amount-1100 where name=' 用户 A'");
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from t_trans_test where name=' 用户 A'");
Map<String, Object> stringObjectMap = maps.get(0);
BigDecimal amount = new BigDecimal(stringObjectMap.get("amount").toString());
if(amount.compareTo(BigDecimal.ZERO) < 0){log.error("余额不反对扣款");
}
}/* 运行后果:=========== 运行前数据筹备重置数据 ========
没有抛出异样测试
发动转账
: 余额不反对扣款
====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=-100}
{id=2, name= 用户 B, amount=500}
*/
4. 数据库引擎不反对
这种状况绝大多数状况根本不会碰到,然而如果真遇到的时候的确很难想到,这里咱们能够间接把表改了之后试了一下:
alter table t_trans_test ENGINE=MyISAM;
批改表之后再进行后面的操作就会发现无论如何操作,最初都是没有方法回滚事务。
5. 非“私有”办法
这个置信简直不会犯错,因为如果你将一个事务注解放到一个不是 public 办法上,局部编辑器比方 IDEA 会间接提醒的,提醒如下:
Methods annotated with '@Transactional' must be overridable
肯定要留神,必须是 public,有的同学可能会说 IDEA 设置成 protected 和默认的 IDEA 也报错然而也没回滚呀,我只能说很多时候不能过于依赖或者置信编译器 …..
(3)事务流传个性实战
上面又是事务的一个重点,也是必须要把握的一个重点,那就是对于事务隔离级别在代码中的理论应用成果,上面咱们将所有的状况都试验一遍:
特点 | 流传级别 | 阐明 |
---|---|---|
反对以后事务的状况: | TransactionDefinition.PROPAGATION_REQUIRED | 如果以后存在事务,则退出该事务;如果以后没有事务,则创立一个新的事务。 |
反对以后事务的状况: | TransactionDefinition.PROPAGATION_SUPPORTS | 如果以后存在事务,则退出该事务;如果以后没有事务,则以非事务的形式持续运行。 |
反对以后事务的状况: | TransactionDefinition.PROPAGATION_MANDATORY | 如果以后存在事务,则退出该事务;如果以后没有事务,则抛出异样。(mandatory:强制性) |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_REQUIRES_NEW | 创立一个新的事务,如果以后存在事务,则把以后事务挂起。 |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 以非事务形式运行,如果以后存在事务,则把以后事务挂起。 |
不反对以后事务的状况: | TransactionDefinition.PROPAGATION_NEVER | 以非事务形式运行,如果以后存在事务,则抛出异样。 |
其余状况 | TransactionDefinition.PROPAGATION_NESTED | 如果以后存在事务,则创立一个事务作为以后事务的嵌套事务来运行;如果以后没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。 |
1. PROPAGATION_REQUIRED
这个级别示意:如果以后存在事务,则退出该事务;如果以后没有事务,则创立一个新的事务。这象征如果存在嵌套的事务操作,默认会以最外层的事务的为准,这里能够验证一下:
/**
* 1。TransactionDefinition.PROPAGATION_REQUIRED 测试
* 不同隔离级别调用
*/
//TransactionDefinition.PROPAGATION_REQUIRED
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void required(){jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
transactionTestService.requiredInner();
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}/* 运行后果:=========== 运行前数据筹备重置数据 ========
====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=1000}
{id=2, name= 用户 B, amount=500}
java.lang.RuntimeException: 回滚事务
*/
// 留神这样应用会抛出异样
// @Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.NEVER)
public void requiredInner(){jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}
从运行后果能够看出,如果在外层抛出事务的异样,哪怕外部的办法看似曾经间接,最终后果仍然会回滚,这也是非常罕用的一种形式,所以被 Spring 设置这个级别为默认级别也能够了解。
2. PROPAGATION_SUPPORTS
如果以后存在事务,则退出该事务;如果以后没有事务,则以非事务的形式持续运行。这种形式也好了解,就是有我就应用事务,没有我就该咋样咋样,比拟随性的形式,和 TransactionDefinition.PROPAGATION_REQUIRED 最大区别就是在没有事务的时候不会被动创立。
这里咱们来看一个比拟奇怪的景象,读者能够思考一下为什么会呈现上面的状况:
/**
*
* Propagation.SUPPORTS 测试
*
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void required(){jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
transactionTestService.requiredInner();
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}/* 运行后果:=========== 运行前数据筹备重置数据 ========
====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=900}
{id=2, name= 用户 B, amount=600}
java.lang.RuntimeException: 回滚事务
*/
public void requiredInner(){jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}
从下面的代码能够看到,咱们将事务的流传个性扭转之后,发现无论是内部还是外部哪怕都抛出了异样,都没有进行回滚也就是说以后 的代码是没有应用事务的!!!如果咱们须要让他们在同一个事务外面,能够做如下调整,上面的代码通过调整之后,在外部的事务中发现外层是存在事务的,所以他会退出到外层的事务当中,然而此时如果把外层的注解去掉,会发现它仍然会非事务形式执行。
/**
* 2。Propagation.SUPPORTS 测试
* 3。*/
@Transactional(rollbackFor = Exception.class)
public void required(){jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
transactionTestService.requiredInner();
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}/* 运行后果:修复之后:=========== 运行前数据筹备重置数据 ========
====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=1000}
{id=2, name= 用户 B, amount=500}
java.lang.RuntimeException: 回滚事务
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void requiredInner(){jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}
能够看到这个流传级别应用比拟容易犯错的,所以如果有必要应用的时候肯定要手动抛出异样本人测试一下。
3. PROPAGATION_MANDATORY
这个级别比较简单粗犷,意思就是必须要有失误,否则我就报错,这里咱们简略批改一下上一个大节的流传级别即可看到成果:
/**
* Propagation.SUPPORTS 测试
*/
// @Transactional(rollbackFor = Exception.class)
public void required(){jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
transactionTestService.requiredInner();
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}/* 运行后果:org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
public void requiredInner(){jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
}
4. PROPAGATION_NOT_SUPPORTED
创立一个新的事务,如果以后存在事务,则把以后事务挂起。这种级别设置意思是说外部的嵌套事务是独立的,如果外部存在失误则独自开启一个事务操作,然而咱们实际操作之后发现竟然报错了,抛出了一个 CannotAcquireLockException 的异样,这个异样搜寻之后发现是数据库的数据行死锁了,为什么会死锁?这里倡议读者能够先本人思考一下而后搜寻下答案,这里就间接说了,这里波及 mysql 的 update 机制,一个 update 的操作应用的是排他锁,此时依照以后的流传级别设置会呈现事务挂起,然而挂起的时候锁是不会开释的,所以在外部办法执行的时候因为始终拿不到锁导致期待超时报错了:
/**
PROPAGATION_NOT_SUPPORTED 测试
*/
@Transactional(rollbackFor = Exception.class)
public void required(){jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
transactionTestService.requiredInner();
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}/* 运行后果:====== 运行后查问操作后果 ======
{id=1, name= 用户 A, amount=1000}
{id=2, name= 用户 B, amount=500}
org.springframework.dao.CannotAcquireLockException: StatementCallback; SQL [update t_trans_test set amount=amount+100 where name='用户 B']; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void requiredInner(){jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}
这里咱们通过调整事务的执行操作程序之后,能够看到在外部的事务回滚了,然而内部的事务没有回滚,这也意味着外部的事务回滚之后内部的事务间接生效了:
@Transactional(rollbackFor = Exception.class)
public void required(){transactionTestService.requiredInner();
jdbcTemplate.update("update t_trans_test set amount=amount-100 where name=' 用户 A'");
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}/* 运行后果:=========== 运行前数据筹备重置数据 ========
====== 运行后查问操作后果 ===== =
{id=1, name= 用户 A, amount=1000}
{id=2, name= 用户 B, amount=600}
java.lang.RuntimeException: 回滚事务
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void requiredInner(){jdbcTemplate.update("update t_trans_test set amount=amount+100 where name=' 用户 B'");
if(EXCEPTIN_FLAG){throw new RuntimeException("回滚事务");
}
}
5. PROPAGATION_NEVER
意思是不能有事务,有事务还会报错,这种状况就不演示了,用注解还不如不必注解,目前集体没看到这个注解的理论应用场景。
6. PROPAGATION_NESTED
如果以后存在事务,则创立一个事务作为以后事务的嵌套事务来运行;如果以后没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。
七、总结
这篇文章的内容比拟多,Spring 的事务在当初看来算是比拟根底的货色,理论看代码发现并不是特地难,如果对于设计模式比拟熟根本能看懂 Spring 的设计,上面咱们来总结一下这篇文章的大抵内容。
事务介绍:在文章的开始咱们介绍了数据库事务的概念,事务是数据库是否风行的要害,事务的个性 ACID,并发事务的问题,事务的次要四个隔离级别,以及简略介绍了事务中保留点的应用,这些是事务最为根底的内容,也是咱们理解 Spring 事务的基础知识。
Spring 事务:Spring 的事务在设计的思考到了不同数据源以及多数据源的事务管理,在一开始咱们重点列举了 Spring 事务的三个外围组件,将一个事务操作形象为三个操作,获取连贯,创立提交事务,回滚事务,这和下面提到的事务操作和个性和相吻合的。接着在事务隔离级别的根底上,Spring 设计了个性也就是事务的流传级别来实现不同的事务个性,在最初能够看一下整个 S print 设计的思路。
介绍完 Spring 的事务之后,开始介绍编程式事务的实现形式和申明式事务的实现形式,同时从源码的角度简略介绍了一下事务设计的外围,比方线程之间的事务隔离是通过 ThreadLocal 进行存储和隔离的,这也是实现嵌套事务的时候应用同一个事务的外围局部,介绍完编程式事务之后介绍了事务的外围申明式事务的,申明式的事务架构要比编程式事务简单不少,须要更多的工夫消化,并且须要肯定的 IOC 容器和主动配置注入的常识才能看懂。
在最初的实战局部,咱们总计了事务不失效的一些坑,和事务流传个性的一次实战,基本上把 Spring 事务注解根本应用场景列举了一下。
写在最初
2022 年第一篇文章,这篇文章也写了比拟久,次要的工夫破费在浏览和了解源代码,不晓得有多少人能够保持到最初的,如果文章有 任何倡议或者谬误欢送指出。
最初新的一年祝读者身体健康,心想事成。
伟人的肩膀
- 如何应用原始的形式进行事务管理:https://cloud.tencent.com/developer/article/1697221
- Spring Transaction Management: @Transactional In-Depth
- Spring 源码剖析:申明式事务梳理