Spring 事务管理(山东数漫江湖)
最新又重新学习了一遍Spring的事务,这里做点总结,不做如何一步步配置的流水账。
1. 关键类
public interface PlatformTransactionManager {
TransactionStatus getTransaction(
TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
事务真正的开始、提交、回滚都是通过PlatformTransactionManager这个接口来实现的,例如,我们常用的org.springframework.jdbc.datasource.DataSourceTransactionManager。
TransactionDefinition用于获取事务的一些属性,Isolation, Propagation,Timeout,Read-only,还定义了事务隔离级别,传播属性等常量。 TransactionStatus用于设置和查询事务的状态,如是否是新事务,是否有保存点,设置和查询RollbackOnly等。
2. 声明式事务
所谓声明式事务,就是通过配置的方式省去很多代码,从而让Spring来帮你管理事务。本质上就是配置一个Around方式的AOP,在执行方法之前,用TransactionInterceptor截取,然后调用PlatformTransactionManager的某个实现做一些事务开始前的事情,然后在方法执行后,调用PlatformTransactionManager的某个实现做commit或rollback. 如图:
声明式事务可以通过XML配置,也可以通过Annotation的方式来配置,还可以两种结合。平时项目中看到比较多的是两种结合的方式,在XML中配置数据源,事务管理器,然后AOP相关的通过@Transactional(该注解可以注在Class,Method上)来配置。(个人感觉,AOP相关的配置用XML配置挺繁琐的,还是注解好)例如:
(readOnly = true)
public class DefaultFooService{
public Foo getFoo(String fooName) {
}
(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
}
}
3. 事务属性
引用官方文档的表格 
- value,在有多个事务管理器存在的情况下,用于标识使用哪个事务管理器
- isolation,事务的隔离级别,默认是Isolation.DEFAULT,这个DEFAULT是和具体使用的数据库相关的。关于隔离级别,可以参考MySQL事务学习总结
- readOnly, 是否只读,如果配置了true,但是方法里使用了update,insert语句,会报错。对于只读的事务,配置为true有助于提高性能。
- rollbackFor, noRollbackFor. Spring的声明式事务的默认行为是如果方法抛出RuntimeException或者Error,则事务会回滚,对于其他的checked类型的异常,不会回滚。如果想改变这种默认行为,可以通过这几个属性来配置。
- propagation, 后面会具体讲。
4. 事务的传播机制
| 类型 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。 |
| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
| PROPAGATION_MANDATOR | 使用当前的事务,如果当前没有事务,就抛出异常 |
| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作 |
其他的都还好理解,后面结合例子重点介绍下PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED三种隔离级别。 表结构和原始数据
mysql> select * from test;
+----+-------+
| id | money |
+----+-------+
| 3 | 500 |
| 5 | 500 |
| 7 | 600 |
+----+-------+
3 rows in set (0.00 sec)
public class MysqlTest01 {
private JdbcTemplate jdbcTemplate;
private MysqlTest02 mysqlTest02;
public void test() {
jdbcTemplate.execute("update test set money = '501' where id = 3");
try {
mysqlTest02.test();
} catch (Exception e) {
System.out.println("第二个事务异常");
}
}
}
class MysqlTest02 {
private JdbcTemplate jdbcTemplate;
(propagation = Propagation.REQUIRED)
public void test() {
jdbcTemplate.execute("update test set money = '502' where id = 3");
throw new RuntimeException();
}
}
执行完之后,test表的数据没有任何变化。 由于MysqlTest02中的事务传播类型是Propagation.REQUIRED,逻辑上有两个事务,但底层是共用一个物理事务的,第二个事务的抛出RuntimeExcetion导致事务回滚,对于这种传播类型,内层事务的回滚会导致外层事务回滚。所以数据库中的数据没有任何变化。
public class MysqlTest01 {
private JdbcTemplate jdbcTemplate;
private MysqlTest02 mysqlTest02;
public void test() {
jdbcTemplate.execute("update test set money = '501' where id = 3");
try {
mysqlTest02.test();
} catch (Exception e) {
System.out.println("第二个事务异常");
}
}
}
class MysqlTest02 {
private JdbcTemplate jdbcTemplate;
(propagation = Propagation.REQUIRES_NEW)
public void test() {
jdbcTemplate.execute("update test set money = '502' where id = 3");
throw new RuntimeException();
}
}
同样的代码,唯一的区别就是第二个事务的传播属性改成了REQUIRES_NEW,执行结果是啥?不好意思,第二个事务执行不了。
对于REQUIRES_NEW,逻辑上有两个事务,底层物理上也有两个事务,由于第一个事务和第二个事务更新的是同一条记录,对于Mysql默认的隔离级别REPEATABLE-READ来说,第一个事务会对该记录加排他锁,所以第二个事务就一直卡住了。
OK,我们把第二个事务的执行的SQL语句换成。
update test set money = '501' where id = 5"
执行结果如下,可以看到只有第二个事务回滚了。
mysql> select * from test;
+----+-------+
| id | money |
+----+-------+
| 5 | 500 |
| 3 | 501 |
| 7 | 600 |
+----+-------+
3 rows in set (0.00 sec)
- PROPAGATION_NESTED 对于这种传播类型,物理上只有一个事务,不过可以有多个savePoint用来回滚。当然是用这种传播类型,需要数据库支持savePoint,使用jdbc的也是要3.0版本以上(这个不太确定)。
public class MysqlTest01 {
private JdbcTemplate jdbcTemplate;
private MysqlTest02 mysqlTest02;
private MysqlTest03 mysqlTest03;
public void test() {
jdbcTemplate.execute("update test set money = '501' where id = 3");
try {
mysqlTest02.test();
} catch (Exception e) {
System.out.println("第二个事务异常");
}
mysqlTest03.test();
}
}
class MysqlTest02 {
private JdbcTemplate jdbcTemplate;
(propagation = Propagation.NESTED)
public void test() {
jdbcTemplate.execute("update test set money = '502' where id = 3");
throw new RuntimeException();
}
}
class MysqlTest03 {
private JdbcTemplate jdbcTemplate;
(propagation = Propagation.NESTED)
public void test() {
jdbcTemplate.execute("update test set money = '503' where id = 3");
}
}
执行结果是如下,可以看到第一个事务和第三个事务提交成功了,第二个事务回滚了。物理上它们是在一个事务里的,只不过用到了保存点的技术。
mysql> select * from test;
+----+-------+
| id | money |
+----+-------+
| 5 | 500 |
| 3 | 501 |
| 7 | 601 |
+----+-------+
3 rows in set (0.01 sec)
5. 其他
在写测试代码的时候遇到了一个关于AOP的问题,可以看到我的测试代码,每个事务都是在一个新的class中写的。为什么不像下面这样写呢?
public class MysqlTest01 {
private JdbcTemplate jdbcTemplate;
public void test01() {
jdbcTemplate.execute("update test set money = '501' where id = 3");
test02();
}
public void test02() {
jdbcTemplate.execute("update test set money = '501' where id = 5");
}
}
这是因为在Spring的AOP中,test01调用test02, test02是不会被AOP截获的,所以也不会被Spring进行事务管理。原因是Spring AOP的实现本质是通过动态代理的方式去执行真正的方法,然后在代理类里面做一些额外的事情。当通过别的类调用MysqlTest01中的test01方法时,因为使用了Spring的DI,注入的其实是一个MysqlTest01的一个代理类,而通过内部方法调用test02时,则不是。
6. Reference
Spring 事务管理(山东数漫江湖)的更多相关文章
- springcloud(一):大话Spring Cloud(山东数漫江湖)
研究了一段时间spring boot了准备向spirng cloud进发,公司架构和项目也全面拥抱了Spring Cloud.在使用了一段时间后发现Spring Cloud从技术架构上降低了对大型系统 ...
- 深入理解Spring MVC(山东数漫江湖)
初始工程 使用Spring Boot和web,thymeleaf的starter来设置初始工程.xml配置如下: <parent> <groupId>org.springf ...
- 透彻理解Spring事务设计思想之手写实现(山东数漫江湖)
前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...
- 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...
- spring事务管理器设计思想(二)
上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...
- spring事务管理器设计思想(一)
在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...
- 事务管理(下) 配置spring事务管理的几种方式(声明式事务)
配置spring事务管理的几种方式(声明式事务) 概要: Spring对编程式事务的支持与EJB有很大的区别.不像EJB和Java事务API(Java Transaction API, JTA)耦合在 ...
- Spring事务管理器的应对
Spring抽象的DAO体系兼容多种数据访问技术,它们各有特色,各有千秋.像Hibernate是非常优秀的ORM实现方案,但对底层SQL的控制不太方便:而iBatis则通过模板化技术让你方便地控制SQ ...
- Spring事务管理(转)
1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是 ...
- [Spring框架]Spring 事务管理基础入门总结.
前言:在之前的博客中已经说过了数据库的事务, 不过那里面更多的是说明事务的一些锁机制, 今天来说一下Spring管理事务的一些基础知识. 之前的文章: [数据库事务与锁]详解一: 彻底理解数据库事务一 ...
随机推荐
- MongoDb Driver For Net
由于mongodb开源github提供的net驱动都比较新,从2.3及以上版本都是netcore系列了,netframework至少都是4.6以上,且提供的dll并没有签名, 这就产生了一些问题 1 ...
- Java入门之:对象和类
Java对象和类 Java作为一种面向对象语言,支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 本节我们重点研究对象和类的概念: 对象: 对象是类的一个实例,有状态和行为.例如 ...
- Luogu1041 NOIP2003传染病控制(搜索)
暴搜加个最优性剪枝即可.一直觉得正式比赛出这种不能一眼看出来暴搜就行了的搜索题的出题人都是毒瘤. #include<iostream> #include<cstdio> #in ...
- WCF服务的建立以及调用
WCF对我来说既陌生又熟悉,陌生是因为没怎么接触过,熟悉是听得太多,今天抽出点时间看了一下WCF,并且自己也写了一WCF的小程序以及调用WCF.步骤为: 1.创建一个解决方案WCF,和一个控制台项目W ...
- [HNOI2007][BZOJ1185] 最小矩形覆盖 [凸包+旋转卡壳]
题面 BZOJ题面 前置芝士 建议先学习向量相关的计算几何基础 计算几何基础戳这里 思路 用这道题学习一下凸包和旋转卡壳 首先是凸包部分 凸包 求凸包用的算法是graham算法 算法流程如下: 找到$ ...
- BZOJ2657:[ZJOI2012]旅游——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=2657 https://www.luogu.org/problemnew/show/P2610 到了难 ...
- [Leetcode] word search 单词查询
Given a 2D board and a word, find if the word exists in the grid. The word can be constructed from l ...
- 洛谷 P2860 [USACO06JAN]冗余路径Redundant Paths 解题报告
P2860 [USACO06JAN]冗余路径Redundant Paths 题目描述 为了从F(1≤F≤5000)个草场中的一个走到另一个,贝茜和她的同伴们有时不得不路过一些她们讨厌的可怕的树.奶牛们 ...
- [LOJ 6159] 最长树链
看到要求gcd不为1所以肯定在这条答案链上都是一个质数的倍数,所以就会产生一个很暴力的想法 没错,正解就是这样的暴力 只让走是i(素数)倍数的点,作最长链 最长链可以树形dp或两遍bfs,一遍找端点, ...
- Linux回收站(改写rm防止误删文件无法恢复)
rm -rf 慎用 命令敲得多了,常在河边走,难免会湿鞋 昨天,一个手误,敲错了命令,把原本想要留的文件夹给rm -rf掉了 几天心血全木有了,靠,死的心都有了 经百度,google以及尝试无果,哎, ...
