Springboot事务控制中A方法调用B方法@Transactional生效与不生效情况实战总结

介绍

本篇对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
  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值