@Transactional没用?事务失效?看看这些坑少挨骂

spring事务失效了? @Transactional不管用了 ?看看这些@Transactional的坑工作不挨骂

@[toc]

1.搭建测试环境

模拟最经典的用户转账 这里用的mybatis-plus 主要是看声明式事务失效的情况 用什么框架不重要

代码如下

@Service
public class DealServiceImpl {


    @Autowired
    private Bank1Service bank1Service;
    @Autowired
    private Bank2Service bank2Service;


    public void deal()
    {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney()-20);
        bank1Service.updateById(bank1);

        //生成异常
           //int i = 10/0;


        bank2.setMoney(bank2.getMoney()+20);
        bank2Service.updateById(bank2);
    }

}

数据库两张表 bank1 bank2

image-20220312185936877

先看业务正常情况

用户1 扣 20 用户 2 加 20 没有问题

idea64_q4zU3NN0uv

如果用户1 扣20 后 业务代码出现异常 需要回滚 我们用声明式事务 在方法上添加@Transactional 测试事务是否能回滚

    @Transactional
    public void deal()
    {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney()-20);
        bank1Service.updateById(bank1);

        int i = 10 /0;

        bank2.setMoney(bank2.getMoney()+20);
        bank2Service.updateById(bank2);
    }

}

idea64_2de8trNsGn

一切正常 如我们所愿事务回滚了 但是在实际开发中,可能会遇到非常多非常多的坑 导致事务不生效

下面一起来看下那些情况会导致事务失效

2.@Transactional失效的三种情况

一.异常不是ERROR 或 RuntimeException及其子类

   @Transactional
    public void deal() throws FileNotFoundException {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney()-20);
        bank1Service.updateById(bank1);

        //将抛出IO异常  非运行时异常
         new FileInputStream(new File("abcd://abcd.text"));

        bank2.setMoney(bank2.getMoney()+20);
        bank2Service.updateById(bank2);
    }

navicat_56HFjdbBVU

什么情况 事务居然没回滚 ?

原因

来看看官方解释

/**
 * Defines zero (0) or more exception {@link Class classes}, which must be
 * subclasses of {@link Throwable}, indicating which exception types must cause
 * a transaction rollback.
 * <p>By default, a transaction will be rolling back on {@link RuntimeException}
 * and {@link Error} but not on checked exceptions (business exceptions). See
 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
 * for a detailed explanation.
 * <p>This is the preferred way to construct a rollback rule (in contrast to
 * {@link #rollbackForClassName}), matching the exception class and its subclasses.
 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
 * @see #rollbackForClassName
 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
 */

什么意思呢

定义零 (0) 个或多个异常类,它们必须是 Throwable 的子类,指示哪些异常类型必须导致事务回滚。默认情况下,事务将在 RuntimeException 和 Error 上回滚,但不会在已检查异常(业务异常)上回滚

不会在非运行时异常上回滚 !!! 但是如果想抛出任何错误或异常 都回滚怎么办 呢

解决办法

改下代码 指定 只要是 Throwable类及其子类都回滚

 @Transactional(rollbackFor = Throwable.class)
    public void deal() throws FileNotFoundException {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney()-20);
        bank1Service.updateById(bank1);


         new FileInputStream(new File("abcd://abcd.text"));

        bank2.setMoney(bank2.getMoney()+20);
        bank2Service.updateById(bank2);
    }

再来测试一下

navicat_qH4rWNTvwP

可以看到事务回滚了

二.异常被吃了

异常被吃了? 什么意思? 看下面这个代码

 @Transactional(rollbackFor = Throwable.class)
    public void deal() throws FileNotFoundException {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney()-20);
        bank1Service.updateById(bank1);


        try {
            //模拟业务执行出现了异常
            new FileInputStream(new File("abcd://abcd.text"));
            bank2.setMoney(bank2.getMoney()+20);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        bank2Service.updateById(bank2);
    }

异常被 try { } catch () { } 包裹了

来,测试下看下是不是能回滚

idea64_waRhaU84IP

结果异常没有回滚

原因

异常被方法内部处理了

解决办法

被事务管理的方法有异常直接抛出去 不要用try {} catch(){} 处理

三 同一个类中非事务管理的方法调用了被事务管理的方法

先看测试代码

@Transactional(rollbackFor = Throwable.class)
    public void deal() throws FileNotFoundException {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney() - 20);
        bank1Service.updateById(bank1);


        //模拟业务执行出现了异常
        new FileInputStream(new File("abcd://abcd.text"));
        bank2.setMoney(bank2.getMoney() + 20);


        bank2Service.updateById(bank2);
    }

创建一个NoTranMethod类 方法没有加 @Transactional 注解

@Componentpublic class NoTranMethod {    @Autowired    private DealServiceImpl dealService;    public void deal() throws FileNotFoundException {        dealService.deal();    }}

测试类

 @Autowired    private NoTranMethod noTranMethod;    @Test    void contextLoads() throws FileNotFoundException {        noTranMethod.deal();    }

image-20220312200850184

测试 非同一个类中非事务管理的方法调用被事务管理的方法生效了

navicat_0NCNXJqV0z

那如果同一个类中会怎么样?

 public void noTranDeal() throws FileNotFoundException {
        deal();
    }

    @Transactional(rollbackFor = Throwable.class)
    public void deal() throws FileNotFoundException {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney() - 20);
        bank1Service.updateById(bank1);


        //模拟业务执行出现了异常
        new FileInputStream(new File("abcd://abcd.text"));
        bank2.setMoney(bank2.getMoney() + 20);


        bank2Service.updateById(bank2);
    }
  @Test    void contextLoads() throws FileNotFoundException {        dealService.noTranDeal();    }

来看结果

navicat_8Ht5SX7lD3

事务没有生效 什么情况??

原因

Spring的声明式事务是基于AOP实现的,而AOP又是基于动态代理, 也就说被事务管理的方法将会走代理方法

但是如果是同一个类中非事务管理的方法调用被事务管理的方法是不会调用代理方法的

解决办法

注入代理对象,调用代理对象的方法 什么意思呢 看代码

@Service
public class DealServiceImpl {


    @Autowired
    private Bank1Service bank1Service;
    @Autowired
    private Bank2Service bank2Service;
    @Autowired
    private DealServiceImpl dealService;


    public void noTranDeal() throws FileNotFoundException {
        dealService.deal();
    }

    @Transactional(rollbackFor = Throwable.class)
    public void deal() throws FileNotFoundException {

        Bank1 bank1 = bank1Service.getById(1);
        Bank2 bank2 = bank2Service.getById(1);
        //模拟转账

        //用户1转账20给用户B

        bank1.setMoney(bank1.getMoney() - 20);
        bank1Service.updateById(bank1);


        //模拟业务执行出现了异常
        new FileInputStream(new File("abcd://abcd.text"));
        bank2.setMoney(bank2.getMoney() + 20);


        bank2Service.updateById(bank2);
    }

}

注意 自动装配了 本类对象 也就是动态代理生成的代理类

能有效果吗?

看演示

navicat_9XJsUsq5uE

事务生效了

重点: Spring 采用动态代理(AOP)生成代理对象,只有在代理对象之间进行调用时,才可以触发切面逻辑。而在同一个类中,方法B调用A,调用的是原对象的方法,而不是通过代理对象,也就保证事务性。

            ⭐️秋日的晚霞⭐️

        ⭐️玲珑骰子安红豆 入骨相思知不知⭐️

💋     如果对你有帮助,给博主一个免费的点赞  💋
👋     博客主页🎉   秋日的晚霞
⭐️     更多文章     请关注主页    📝
👋     一起走上java学习之路!
🎄     欢迎各位🔎点赞👍评论收藏⭐️     🎄

爱你哦

#java求职##学习路径##社招##校招##Java##春招#
全部评论
异常被吃 不是public 也有可能传播配置错误 数据库引擎不支持事物 或者bean没有注入给spring
1 回复 分享
发布于 2022-03-15 11:15
我就不喜欢搭建环境,太麻烦了
点赞 回复 分享
发布于 2022-03-14 14:44

相关推荐

生命诚可贵:先不说内容怎么样 排版就已经太差劲了 第一眼看不到重点,第二眼已经没有再看的耐心了, 篇幅占的太满了 字体不要用灰色 观感不好 想重点突出的黑色加粗就可以了 多列要点 少些大段的句子 项目经历把项目用的技术要点列出来,光写个python plc什么的太宽泛了 自我评价也有点偏多
点赞 评论 收藏
分享
评论
1
14
分享

创作者周榜

更多
牛客网
牛客企业服务