介绍
本篇对Springboot事务控制中A方法调用B方法@Transactional生效与不生效情况进行实战总结,让容易忘记或者困扰初学者甚至老鸟的开发者,只需要看这一篇文章即可立马找到解决方案,这就是干货的价值。喜欢的朋友别忘记来个一键三连哈:)
实战步骤
由于A方法调用B方法的情况较多,此处按照 一定的命名规则复现各种情况。
例如:
c代表class,c0代表不同类,c1代表同类
a代表a方法,a0代表a方法无事务注解,a1代表有
b代表b方法,b0代表b方法无事务注解,b1代表有
e代表抛异常,ea代表a方法中抛异常;eb代表b方法执行抛异常;
组合起来:c1a0b1ea
表示:同类中a调用b,b上有事务,a中抛异常
创建表
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
创建测试类
AbstractUserService执行事务操作
@Service
public class AbstractUserService {
@Autowired
private UserService userService;
/**
* 新增用户
* @param username
*/
public void saveUser(String username) {
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
userService.save(userEntity);
}
/**
* 更新密码
* @param username
*/
public void updatePassword(String username) {
UserEntity entity = userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername,username));
entity.setPassword("123456");
userService.updateById(entity);
}
/**
* 制造异常
*/
public void makeException() {
int i=1/0;
}
}
Service01 代表a类
@Service
public class Service01 extends AbstractUserService{
@Autowired
private Service02 service02;
// 同类调用
public void c1_a0_b1_ea(String username){
saveUser(username);
this.b1(username,false);
makeException();
}
public void c1_a0_b1_eb(String username){
saveUser(username);
this.b1(username,true);
}
@Transactional(rollbackFor = Exception.class)
public void c1_a1_b0_ea(String username){
saveUser(username);
this.b0(username,false);
makeException();
}
@Transactional(rollbackFor = Exception.class)
public void c1_a1_b0_eb(String username){
saveUser(username);
this.b0(username,true);
}
@Transactional(rollbackFor = Exception.class)
public void c1_a1_b1_ea(String username){
saveUser(username);
this.b1(username,false);
makeException();
}
@Transactional(rollbackFor = Exception.class)
public void c1_a1_b1_eb(String username){
try{
saveUser(username);
this.b1(username,true);
}catch (Exception e){
System.out.println("c1_a1_b1_eb执行失败:");
throw new RuntimeException("c1_a1_b1_eb执行失败");
}
}
// 不同类调用
public void c0_a0_b1_ea(String username){
saveUser(username);
service02.b1(username,false);
makeException();
}
public void c0_a0_b1_eb(String username){
saveUser(username);
service02.b1(username,true);
}
@Transactional(rollbackFor = Exception.class)
public void c0_a1_b0_ea(String username){
saveUser(username);
service02.b0(username,false);
makeException();
}
@Transactional(rollbackFor = Exception.class)
public void c0_a1_b0_eb(String username){
saveUser(username);
service02.b0(username,true);
}
@Transactional(rollbackFor = Exception.class)
public void c0_a1_b1_ea(String username){
saveUser(username);
service02.b1(username,false);
makeException();
}
@Transactional(rollbackFor = Exception.class)
public void c0_a1_b1_eb(String username){
saveUser(username);
service02.b1(username,true);
}
public void b0(String username,boolean hasException){
updatePassword(username);
if(hasException){
makeException();
}
}
@Transactional(rollbackFor = Exception.class)
public void b1(String username, boolean hasException){
updatePassword(username);
if(hasException){
makeException();
}
}
}
Service02 代表b类
@Service
public class Service02 extends AbstractUserService{
public void b0(String username,boolean hasException){
updatePassword(username);
if(hasException){
makeException();
}
}
@Transactional(rollbackFor = Exception.class)
public void b1(String username, boolean hasException){
updatePassword(username);
if(hasException){
makeException();
}
}
}
测试接口TestController
@RestController
@RequestMapping("")
public class TestController {
@Autowired
private Service01 service01;
/**
* 同类
* a没有事务,b有 ,异常发生在a中 不会回滚
* @return
*/
@GetMapping("/c1_a0_b1_ea")
public String test1() {
service01.c1_a0_b1_ea("c1_a0_b1_ea");
return "ok";
}
/**
* 同类
* a没有事务,b有 ,异常发生在b中 不会回滚
* @return
*/
@GetMapping("/c1_a0_b1_eb")
public String test2() {
service01.c1_a0_b1_eb("c1_a0_b1_eb");
return "ok";
}
/**
* 同类
* a有事务,b没有 ,异常发生在a中 会回滚
* @return
*/
@GetMapping("/c1_a1_b0_ea")
public String test3() {
service01.c1_a1_b0_ea("c1_a1_b0_ea");
return "ok";
}
/**
* 同类
* a有事务,b没有 ,异常发生在b中 会回滚
* @return
*/
@GetMapping("/c1_a1_b0_eb")
public String test4() {
service01.c1_a1_b0_eb("c1_a1_b0_eb");
return "ok";
}
/**
* 同类
* a有事务,b有 ,异常发生在a中 会回滚
* @return
*/
@GetMapping("/c1_a1_b1_ea")
public String test5() {
service01.c1_a1_b1_ea("c1_a1_b1_ea");
return "ok";
}
/**
* 同类
* a有事务,b有 ,异常发生在b中 会回滚
* @return
*/
@GetMapping("/c1_a1_b1_eb")
public String test6() {
service01.c1_a1_b1_eb("c1_a1_b1_eb");
return "ok";
}
/**
* 不同类
* a没有事务,b有 ,异常发生在a中 不会回滚
* @return
*/
@GetMapping("/c0_a0_b1_ea")
public String test7() {
service01.c0_a0_b1_ea("c0_a0_b1_ea");
return "ok";
}
/**
* 不同类
* a没有事务,b有 ,异常发生在b中 只有b回滚
* @return
*/
@GetMapping("/c0_a0_b1_eb")
public String test8() {
service01.c0_a0_b1_eb("c0_a0_b1_eb");
return "ok";
}
/**
* 不同类
* a有事务,b没有 ,异常发生在a中 会回滚
* @return
*/
@GetMapping("/c0_a1_b0_ea")
public String test9() {
service01.c0_a1_b0_ea("c0_a1_b0_ea");
return "ok";
}
/**
* 不同类
* a有事务,b没有 ,异常发生在b中 会回滚
* @return
*/
@GetMapping("/c0_a1_b0_eb")
public String test10() {
service01.c0_a1_b0_eb("c0_a1_b0_eb");
return "ok";
}
/**
* 不同类
* a有事务,b有 ,异常发生在a中 会回滚
* @return
*/
@GetMapping("/c0_a1_b1_ea")
public String test11() {
service01.c0_a1_b1_ea("c0_a1_b1_ea");
return "ok";
}
/**
* 不同类
* a有事务,b有 ,异常发生在b中 会回滚
* @return
*/
@GetMapping("/c0_a1_b1_eb")
public String test12() {
service01.c0_a1_b1_eb("c0_a1_b1_eb");
return "ok";
}
}
测试结果
http://localhost:9000/test/c0_a1_b1_eb
在浏览器中依次访问测试接口中的每个方法,得到表中结果:
以下情况未回滚或者半回滚,不在表里的均正常回滚事务。
原理总结
原理:
spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。
此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。
那回到一开始的问题,我们调用的方法A不带注解,因此代理类不开事务,而是直接调用目标对象的方法。当进入目标对象的方法后,执行的上下文已经变成目标对象本身了,因为目标对象的代码是我们自己写的,和事务没有半毛钱关系,此时你再调用带注解的方法,照样没有事务,只是一个普通的方法调用而已。
简单来说,内部调用本类方法,不会再走代理了,所以B的事务不起作用。
如果AB不同类,A调用的事代理类B,故B有事务。
参考文章
- https://blog.csdn.net/weixin_36586564/article/details/105687331
- https://juejin.cn/post/7031446300142862373
- 【@Transactional注解失效的几种情况】
https://blog.csdn.net/Yaml4/article/details/138123693