spring中的事务传播机制的demo、事务7种传播行为Demo、@Transation常见面试题、@Transactional注解啥时候下失效?、一个service调用另一个service的事务传播行为、serviceA调用serviceB,serviceA中加了Transaction,serviceB没加,如果a、b有一个报错会怎样、@transtational可以加在私有方法中吗?、spring的事务传播行为,同一个类中,a事务正常,b方法报错了,这个整体是回滚还是提交、@Transaction详解、@Transaction中调用方报错,被调用方会回滚事务嘛、service调用另一个service报错了,两个都会回滚吗
文章目录
- 前言
- 一、什么是事务?
- 二、demo地址以及前置工作
- 三、demo代码。
- 1.Transactional(propagation = Propagation.REQUIRED)
- 2、Transactional(propagation = Propagation.REQUIRED_NEW)
- 3、Transactional(propagation = Propagation.SUPPORTS
- 4、@Transactional(propagation = Propagation.NOT_SUPPORTED
- 5、Transactional(propagation = Propagation.MANDATORY)
- 6、Transactional(propagation = Propagation.NEVER)
- 7、Transactional(propagation = Propagation.NESTED)
- 8、注:我上面A、B是在两个类中,同一个类中A、B方法不会回滚和提交!!!)
- 四、日常开发中常用的方式
- 五、Transaction常见面试题
- 总结
前言
之前部门内部开会,复盘线上事故时,同事有个bug,排查的时候发现是由于对spring中的事务传播行为理解的不清晰导致的。因此写篇文章,分享出来的同时,也能让我再次熟悉一下事务传播。本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!
一、什么是事务?
事务的ACID四大特性,咱们这次主要针对于A原子性。原子性,简单的说,我要做两件事,要么同时成功,要么同时失败,不能是一件成功一件失败。如果第一件成功了,第二件失败了,那我会让第一件也失败,这就是事务。其实能搜到这篇文章的,大家应该都知道,直接看demo吧。
二、demo地址以及前置工作
1、git地址
项目代码路径是:git地址有需求的可以自行下载。
2、7种传播行为
-
REQUIRED:spring默认的事务传播级别,当前存在事务,则加入该事务,当前不存在事务,则新建一个事务。
-
REQUIRED_NEW:如果当前没有事务,则新建一个事务。如果当前存在事务,则新建一个事务。(不管原先有没有,都会新建事务)。新老事务相互独立,外部事务抛出异常回滚不会影响内部事务的正常提交。
-
SUPPORTS:如果调用方(A)有事务时,被调用方(B)加入到A的事务中;如果A没有事务,则以非事务的方式运行。
-
NOT_SUPPORTED:以非事务的方式运行,如果有事务,则将当前事务挂起。
-
MANDATORY:强制事务执行,如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
-
NEVER:以非事务方式运行,如果不存在事务,以非事务方式运行。如果存在事务,则抛出异常。
-
NESTED:NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务。
3、调用顺序
当A方法调用B方法时,流程如下。下面demo中的A方法指UserService,B方法指TeacherService , 也就是调用方指A,被调用方指B。
三、demo代码。
1.Transactional(propagation = Propagation.REQUIRED)
spring默认的事务传播级别,当前存在事务,则加入该事务,当前不存在事务,则新建一个事务。
1.1、外部(调用方,A方法)无事务
测试:外部无事务,内部有事务,内部事务是required。外部抛异常。
结论:外层方法没有事务时,即使外层方法报错,两次提交都不会回滚。
因为外层没有事务时,insert(user的insert)会自动提交(由于连接的是mysql数据库,所以会自动提交),所以你insert之后的异常,是没有用的,已经提交了。
而Teacher的insert,是由于teacher成功了,所以teacher他也提交了(由于连接的是mysql数据库,所以会自动提交),导致的现象就是,即使方法报错了,也两次都提交了。
/**
* @author: ztl
* @date: 2022/12/27 11:18
* @desc: 测试:外部无事务,内部有事务,内部事务是required。外部抛异常。
* 结论:外部、内部都不会回滚。
* 测试User(A)没有事务,teacher(B)会自己创建事务。
* 这里没有开启事务,teacherService.insert(); 会自己开启事务。
* teacherService.insert()方法上有@Transactional(propagation = Propagation.REQUIRED)
* 而且俩个添加操作都会成功。因为userMapper.insert(user);是自动提交的(因为这个insert0方法没开启事务,又是mysql,所以自动提交了)
*
* 因为调用方的A方法(insert0)没有事务,所以 userMapper.insert(user);会自动提交,因为没有事务,且是mysql默认级别是可重复读,自动提交
* 而teacherService.insert();虽然加了注解,开启了事务,但是由于他成功了,所以他也提交了。
* 所以尽管insert0方法最后报错了,但是由于这俩都各自提交了,所以并不会回滚。
*/
@Override
public void insert0(){
teacherService.insert();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,两个提交操作都不会回滚(会成功)
}
============= teacherService.insert();方法如下: ==========
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
return teacherMapper.insert(teacher);
}
1.2、外部(调用方,A方法)有事务
测试:外部有事务,是required,内部有事务,是required。外部抛异常。
结论:外部、内部都会回滚事务。
当外层的,调用方的方法(A方法)有事务时,被调用方(B方法)即使有事务,也会加入到当前的事务中,所以他们是同一个事务,都是insert1方法的事务,所以当有异常时,两个都会回滚。
/**
* @author: ztl
* @date: 2022/12/28 10:20
* @desc: 测试:外部有事务,是required,内部有事务,是required。外部抛异常。
* 结论:外部、内部都会回滚事务。
* A(调用方,也就是insert1)方法加了@Transactional注解,会开启事务。
* teacherService.insert(); 也加了@Transactional注解,也会开启事务
* 由于user的insert1方法有事务了,而且teacherService.insert()的类型是REQUIRED,
* 所以teacher的事务会加入到user的事务(insert1方法的事务),就是同一个事务了。
* 同一个事务中,有异常,所以两个都会回滚
*
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert1(){
teacherService.insert();
User user = new User(2,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,两个提交操作都会回滚(不会成功)
}
============= teacherService.insert();方法如下: ==========
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
return teacherMapper.insert(teacher);
}
2、Transactional(propagation = Propagation.REQUIRED_NEW)
2.1、外部(调用方,A方法)抛异常
REQUIRED_NEW的意思是,如果当前没有事务,则新建一个事务。如果当前存在事务,则新建一个事务。(不管原先有没有,都会新建事务)。新老事务相互独立,外部事务抛出异常回滚不会影响内部事务的正常提交。
测试:外部有事务,是required,内部有事务,是required_new。外部抛异常
结论:外层insert2报错,teacherService.insert2不会回滚,userMapper.insert会回滚。
内部事务是required_new的传播行为时,会新建一个事务,即使外部事务回滚也不会影响内部事务的提交。外层方法开启事务时,即使外层方法报错,内部的事务不会回滚,依然会提交。
注意:外部方法是 required,内部方法是 required_new
/**
* @author: ztl
* @date: 2022/12/27 11:18
* @desc: 测试:外部有事务,是required,内部有事务,是required_new。外部抛异常
* 结论:外层insert2报错,teacherService.insert2不会回滚,userMapper.insert会回滚。
* teacherService.insert2()的传播行为是propagation = Propagation.REQUIRES_NEW
* 由于调用方(A,也就是insert2)加了Transactional,开启了事务,被调用方(B,也就是teacherService.insert2)也开启了事务,
* 并且B的传播行为是REQUIRED_NEW,不管当前有没有事务,都会新建一个事务,并且外部事务回滚不会影响内部事务提交。
* 所以teacherService.insert2()新建了一个新事务,并且新事务成功,所以会提交。
* 即使外部user的insert2方法异常了,也不影响内部teacher的insert2方法的提交
* 总的流程如下:
* user的insert2开启了一个事务。
* teacher的insert2开启了一个事务,并且将user的insert2的事务挂起
* user的insert2的事务运行完毕,再恢复user的insert2的事务,
* 两个事务的提交和回滚都在各自的事务中完成。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert2(){
teacherService.insert2();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,两个提交操作都会回滚(不会成功)
}
============= teacherService.insert();方法如下: ==========
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insert2() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
return teacherMapper.insert(teacher);
}
2.2、内部(被调用方,B方法)抛异常
测试:外部有事务,是required。内部有事务,是required_new,在内部抛出异常。
结论:外部、内部都会回滚。
虽然是俩事务,但是内部事务异常回滚了,内部会将异常抛出去,抛到外层,相当于外部调用那行直接new throw Exception,所以会回滚。
/**
* @author: ztl
* @date: 2022/12/27 11:18
* @desc: 测试:外部有事务,是required。内部有事务,是required_new,在内部抛出异常。
* 结论:外部、内部都会回滚。虽然是俩事务,但是内部事务异常回滚了,内部会将异常抛出去,相当于外部也有异常了。
* 外部有事务,是required,内部有事务,是required_new.
* 内部事务开启,将外部事务挂起,内部事务异常,内部事务回滚。并且,内部将异常抛出到外部,
* 内部事务执行完毕,将外部事务从挂起恢复,外部事务异常(是由内部抛出来的),外部事务回滚。
* 其实teacherService.insert3()异常,就相当于直接在这行代码下面,写一句:
* throw new Exception..,所以,内部、外部都会回滚。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert3(){
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
// 这个方法会抛出异常,就相当于直接在这个代码的下面写: int i = 1/0,也就相当于直接在这段下面 throw new Exception..
teacherService.insert3();
}
============= teacherService.insert();方法如下: ==========
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int insert3() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
int i = 1/0; // 这里抛出异常,内部有异常的话,会将异常抛出去,所以外部也会有异常了。
return insert;
}
3、Transactional(propagation = Propagation.SUPPORTS
如果调用方(A)有事务时,被调用方(B)加入到A的事务中;如果A没有事务,则以非事务的方式运行。
3.1、外部(调用方,A方法)有事务
测试:外部有事务REQUIRED,内部有事务SUPPORTS,外部抛异常
结论:两个都回滚。
/**
* @author: ztl
* @date: 2022/12/29 16:54
* @desc: 外部有事务REQUIRED,内部有事务SUPPORTS,外部抛异常
* 结论:两个都回滚。
* SUPPORTS是,外部有事务,则加入该事务,外部无事务,则以非事务方式运行
* 外部方法user.insert4 有Transactional注解,则开启了事务,属于外部有事务
* 内部teacherService.insert4()是supports,会直接加入到外部事务,是同一个事务了。
* 同一个事务,发生异常,回滚,所以俩都回滚了。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert4(){
teacherService.insert4();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public int insert4() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
3.2、外部(调用方,A方法)无事务
测试:外部无事务,内部有事务且为supports类型。外部抛出异常
结论:两者都不会回滚,都提交了。
/**
* @author: ztl
* @date: 2022/12/29 17:08
* @desc: 外部无事务,内部有事务且为supports类型。外部抛出异常
* 结论:两者都不会回滚,都提交了。
* supports是,外部有事务,则加入该事务;外部无事务,则以非事务的方式运行。
* 外部无事务(user的insert5方法没加Transactional),则以非事务运行。
* teacherService.insert5以非事务,又是mysql数据库,mysql是自动提交,insert5方法没报错,则提交了
* user。insert也一样,insert方法没报错,则自动提交了。
* 都提交完事务了才报错,跟他俩就没关系了,因为已经提交了,所以俩都不会回滚。
*/
@Override
public void insert5(){
teacherService.insert5();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public int insert5() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
4、@Transactional(propagation = Propagation.NOT_SUPPORTED
以非事务的方式运行,如果有事务,则将当前事务挂起。
4.1、外部(调用方,A方法)有事务
测试:外部有事务,内部有事务,内部事务NOT_SUPPORTED,外部抛异常
结论:外部(user)insert回滚,内部(teacher)insert提交。
/**
* @author: ztl
* @date: 2023/01/03 10:35
* @desc: 外部有事务,内部有事务,内部事务NOT_SUPPORTED,外部抛异常
* 结论:外部(user)insert回滚,内部(teacher)insert提交。
* NOT_SUPPORTED,不支持事务,如果有事务,则挂起该事务并以非事务运行。
* 外部user。insert加了注解Transactional,开启了事务。
* 内部teacher加了注解,并且是not_supported,
* 内部挂起外部的事务,并以非事务方式运行,非事务,mysql数据库,自动提交了
* 内部运行完毕,已经提交的前提下,恢复外部事务,外部事务遇到异常,外部事务回滚。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert6(){
teacherService.insert6();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 外部调用方有事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int insert6() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
4.2、外部(调用方,A方法)无事务
测试:外部无事务,内部有事务,内部not_supported,外部抛出异常
结论:两个都提交
/**
* @author: ztl
* @date: 2022/12/27 11:18
* @desc: 外部无事务,内部有事务,内部not_supported,外部抛出异常
* 结论:两个都提交。
* not_supported,以非事务运行,如果有事务则挂起,以非事务运行。
* 外部无事务,内部not_supports,都是没事务,都自动提交,
* 因为mysql是自动提交的,都已经提交完再抛出异常,所以俩都提交。
*/
@Override
public void insert7(){
teacherService.insert7();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 外部调用方无事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int insert7() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
5、Transactional(propagation = Propagation.MANDATORY)
强制事务执行,如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常
5.1、外部(调用方,A方法)有事务
测试:外部有事务,内部有事务,内部是MANDATORY,外部抛出异常
结论:内部、外部都回滚。(抛出的是外部的异常by zero)
/**
* @author: ztl
* @date: 2023/01/03 14:36
* @desc: 外部有事务,内部有事务,内部是MANDATORY,外部抛出异常
* 结论:内部、外部都回滚。(抛出的是外部的异常by zero)
* mandatory是,强制事务执行,如果有事务,加入该事务;如果没有事务,则抛出异常。
* 内部事务是MANDATORY,如果外部有事务,则内部事务加入到外部事务;
* 如果外部没事务,内部事务抛出异常。
* 外部加了Transactional注解,开启了事务,内部的(teacher)事务会加入,
* 加入了以后,是一个事务,同一个事务内,出现异常,回滚,两个都回滚。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert8(){
teacherService.insert8();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.MANDATORY)
@Override
public int insert8() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
5.2、外部(调用方,B方法)无事务
测试:外部无事务,内部有事务,mandatory,外部抛异常,
结论:外部、内部全部回滚(抛出的是内部的异常,no existing transaction for ‘mandatory’)
/**
* @author: ztl
* @date: 2023/01/03 11:18
* @desc: 外部无事务,内部有事务,mandatory,外部抛异常,
* 结论:外部、内部全部回滚(抛出的是内部的异常,no existing transaction for 'mandatory')
* mandatory是,强制事务执行,若当前存在事务,则加入该事务;若当前不存在事务,则抛出异常。
* 外部有事务,则加入该事务,外部没事务,直接抛出异常。
* 所以即使你 1/0 了,报错也不是 by zero,而是:
* org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
* 由于teacher(内部)抛出了异常,所以内部回滚。内部抛出去了,异常到外部了,
* 外部有异常,所以外部也回滚了。
*/
@Override
public void insert9(){
teacherService.insert8();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.MANDATORY)
@Override
public int insert8() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
6、Transactional(propagation = Propagation.NEVER)
以非事务方式运行,如果不存在事务,以非事务方式运行。如果存在事务,则抛出异常。
6.1、外部(调用方,A方法)有事务
测试:外部有事务,内部有事务,内部为never,外部抛出异常
结论:内部、外部都回滚。不会提交。(异常是Existing transaction for ‘never’)
/**
* @author: ztl
* @date: 2023/01/03 15:08
* @desc: 外部有事务,内部有事务,内部为never,外部抛出异常
* 结论:内部、外部都回滚。不会提交。(异常是Existing transaction for 'never')
* never是,以非事务方式运行,如果当前不存在事务,以非事务运行;如果当前存在事务,则抛出异常。异常是:
* org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
* 外部加了transaction注解,外部有事务,内部是never,never如果存在事务,则抛出异常,内部抛出异常,内部回滚
* 内部抛出的异常到外部了,外部遇到异常,直接不往下执行了,user的insert不会进行。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert11(){
teacherService.insert10();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.NEVER)
@Override
public int insert10() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
6.2、外部(调用方,A方法)无事务
测试:外部无事务,内部有事务,内部为never,外部抛出异常
结论:两个都不回滚,都提交。(异常是by zero)
/**
* @author: ztl
* @date: 2023/01/03 15:02
* @desc: 外部无事务,内部有事务,内部为never,外部抛出异常
* 结论:两个都不回滚,都提交。(异常是by zero)
* never是,以非事务方式运行,如果当前存在事务,则抛出异常,如果当前不存在事务,以非事务方式运行。
* 外部没加transaction,所以外部无事务,内部为never,所以以非事务方式运行。
* 非事务运行,内部(teacher)成功,直接提交。
* 非事务运行,外部(user)因为是连接的mysql数据库,自动提交,所以也提交,提交完了才报错,不影响,所以不回滚。
*/
@Override
public void insert10(){
teacherService.insert10();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.NEVER)
@Override
public int insert10() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
7、Transactional(propagation = Propagation.NESTED)
NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务。
7.1、外部(调用方,A方法)抛异常
测试:外部有事务,内部有事务,内部NESTED,外部抛异常
结论:外部、内部、全部回滚(外部事务异常,会导致嵌套内事务同步回滚)。
/**
* @author: ztl
* @date: 2023/01/09 15:22
* @desc: 外部有事务,内部有事务,内部NESTED,外部抛异常
* 结论:外部、内部、全部回滚(外部事务异常,会导致嵌套内事务同步回滚)。
* NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务
* 外部加了Transaction,外部有事务,内部是nested,内部事务会嵌套在外部事务中。
* 内部teacher成功,提交了,外部事务user失败了,会导致内部的teacher同步回滚。
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert12(){
try {
teacherService.insert12();
}catch (Exception e){
e.printStackTrace();
System.out.printf("exception:"+e);
}
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert12() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
7.2、内部(被调用方,B方法)抛异常
测试:外部有事务required,内部有事务nested,内部抛异常
结论:外部提交,内部回滚
/**
* @author: ztl
* @date: 2023/01/09 15:41
* @desc: 外部有事务required,内部有事务nested,内部抛异常
* 结论:外部提交,内部回滚
* NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务
* 说明外层的异常可以影响到内层的事务,内层的事务影响不到外部的事务。
* teacher异常,被回滚了,teacher抛到外层user这层的,被捕获了,没异常,提交
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert13(){
try {
teacherService.insert13();
}catch (Exception e){
e.printStackTrace();
System.out.printf("exception:"+e);
throw new RuntimeException("123");
}
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
}
============= teacherService.insert();方法如下: ==========
// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert13() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
int i = 1/0; // 这里抛出异常,
return insert;
}
7.3、内部(被调用方,B方法)、外部都不抛异常
测试:外部有事务required,内部有事务nested,内部外部都不抛异常
结论:内部、外部都提交
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert14(){
teacherService.insert14();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
}
============= teacherService.insert();方法如下: ==========
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert14() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
7.4、requires_new和nested区别测试demo
7.4.1、外部(调用方,A方法)抛异常
测试:外部有事务required,内部有事务,分别为nested、requires_new。外部抛异常
结论:当为nested时,外部异常,内部、外部都回滚。当为requires_new时,外部异常。外部回滚,内部提交。
/**
* @author: ztl
* @date: 2022/12/27 11:18
* @desc: 测试nested和requires_new区别:
* 外部有事务required,内部有事务,分别为nested、requires_new。外部抛异常,
* 结论:当为nested时,外部异常,内部、外部都回滚。当为requires_new时,外部异常。外部回滚,内部提交
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert17(){
try {
// teacherService.insert17(); // 外部required,内部nested,外部抛异常,内部、外部都回滚
teacherService.insert18(); // 外部required,内部requires_new,外部抛异常,外部回滚,内部提交
}catch (Exception e){
e.printStackTrace();
System.out.printf("exception:"+e);
}
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
// 测试nested和requires_new的区别,外部抛异常
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert17() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
// 测试nested和requires_new的区别,外部抛异常
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int insert18() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
7.4.2、内部(被调用方,B方法)抛异常
测试:外部有事务required,内部有事务,内部事务分别为nested、requires_new。内部抛异常,外部catch住。
结论:内部都回滚,外部都提交。nested、requires_new在内部有异常外部无异常时,这俩一样
/**
* @author: ztl
* @date: 2023/01/16 16:26
* @desc: 测试nested和requires_new区别:
* 外部有事务required,内部有事务,分别为nested、requires_new。内部抛异常,外部catch住,
* 结论:内部都回滚,外部都提交。nested、requires_new在内部有异常外部无异常时,这俩一样
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert15(){
try {
// teacherService.insert15(); // 外部required,内部nested,内部抛异常,外部提交,内部回滚
teacherService.insert16(); // 外部required,内部requires_new,内部抛异常,外部提交,内部回滚
}catch (Exception e){
e.printStackTrace();
System.out.printf("exception:"+e);
}
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
}
============= teacherService.insert();方法如下: ==========
// 测试nested和requires_new的区别,内部抛异常
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert15() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
int i = 1/0; // 这里抛出异常,
return insert;
}
// 测试nested和requires_new的区别,内部抛异常
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int insert16() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
int i = 1/0; // 这里抛出异常,
return insert;
}
8、注:我上面A、B是在两个类中,同一个类中A、B方法不会回滚和提交!!!)
四、日常开发中常用的方式
1、无事务场景
测试:内部、外部都没事务,外部抛异常,
结论:内部外部都提交
由于连接的是mysql数据库,自动提交,是提交完以后才报的错,已经提交过了,所以不会回滚,会提交。
/**
* @author: ztl
* @date: 2023/01/16 16:46
* @desc: 内部、外部都没事务,外部异常,内部外部都提交
* 因为连接的是mysql数据库,会自动提交,都已经提交完了才报错,所以两个都不会回滚
*/
@Override
public void insert19(){
teacherService.insert19();
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
int i = 1/0; // 这里抛出异常,
}
============= teacherService.insert();方法如下: ==========
@Override
public int insert19() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
2、(最常用)外部@Transaction,内部无事务(无注解)
外部加了@Transaction注解,所以开启了事务,未指定propagation,即是默认required。外部加了注解,开启了事务,required,内部无事务。
不管是外部、内部,任何一个地方有异常,则内部、外部全部回滚。
测试:外部抛异常
结论:内部、外部都回滚
测试:内部抛异常
结论:内部、外部都回滚
/**
* @author: ztl
* @date: 2023/01/16 16:46
* @desc: 外部有事务,required,内部无事务,
* 外部抛异常:内部、外部都回滚。
* 内部抛异常:
*/
@Transactional
@Override
public void insert20(){
teacherService.insert19(); // 外部异常,内部无事务,内部、外部都要回滚。
// teacherService.insert20(); // 内部异常,内部无事务,内部、外部都要回滚
User user = new User(1,"user_"+System.currentTimeMillis());
userMapper.insert(user);
// int i = 1/0; // 这里抛出异常,
}
@Override
public int insert19() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
return insert;
}
@Override
public int insert20() {
Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
int insert = teacherMapper.insert(teacher);
int i = 1/0; // 这里抛出异常,
return insert;
}
五、Transaction常见面试题
1、spring的事务传播行为,同一个类中,a事务正常,b方法报错了,这个整体是回滚还是提交
在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.是因为spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!
如果spring的同一个类中,a方法有事务,b方法没有事务,那么a,b之间,事务没法传播,都不会自动的传播事务,需要自己手动提交或回滚。
不管循环嵌套了多少个事务,只要最上层的类有事务控制,那么不管是哪个方法调用异常,都会回滚(前提是不是同一个类)。
如果一个有事务的方法,去调用了没有事务的方法,会走事务;如果一个没有事务的方法,调用了带有事务的方法,是不会有事务的。
因为@Transational注解是,给当前类生成一个子类,然后在子类执行响应的业务逻辑前,先加上事务,如果执行结束,就提交或者回滚。而如果同一个类的话,事务方法是在当前类的子类(代理类)里面执行的,普通方法是在当前类中执行的,都不是一个类,肯定没法统一的事务处理。
要解决的话:可以在类上加@Transactional而不是在类的方法中加上,或者你在嵌套调用的时候,加一个try,catch,用来捕获,并做处理。
spring的默认事务规则(7种传播行为)是,如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新事务。
2、spring的@Transactional注解啥时候下失效?
私有方法、同一个类中、@Transactional 注解,不支持静态方法
3、@transtational可以加在私有方法中吗
方法可见性和@Transactional在使用代理时,应用@Transactional只对具有公共可见性的方法进行注释。如果确实对受保护的、私有的或包可见的方法进行注释,则使用@Transactional注释,不会引发错误,但带注释的方法不会显示已配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。@Transactional修饰static方法时,会报错
原理: spring
在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。
总结
到这里大家应该对spring中的传播行为有了一定的了解了。建议平时开发的时候多测测,这样即使线上环境真出问题了,也会同步失败,不会造成大量的脏数据,也不会有很严重的生产事故。以后有时间了,还会从源码的角度,分析为何@Transaction会起作用。以及何时不会起作用。