spring中@Transactional 注解失效的原因及解决办法

简介:大家好,我是程序员枫哥,🌟一线互联网的IT民工、📝资深面试官、🌹Java跳槽网创始人。拥有多年一线研发经验,曾就职过科大讯飞、美团网、平安等公司。在上海有自己小伙伴组建的副业团队,目前业余时间专注Java技术分享,春招/秋招/社招/跳槽,一对一学习辅助,项目接活开发。
🌈更多学习内容, 欢迎👏关注👀【文末】微信公众号:IT枫斗者
🌟🌟程序员找工作,就上Java跳槽网:www.javatiaocao.com

spring中@Transactional 注解失效的原因及解决办法

@Transactional 属性介绍

事务的传播行为:propagation

  • 这就是我们常说的事务的七种传播行为,默认值:Propagation.REQUIRED

  • 属性解释
    Propagation.REQUIRED如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
    Propagation.SUPPORTS如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
    Propagation.MANDATORY如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
    Propagation.REQUIRES_NEW重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
    Propagation.NOT_SUPPORTED以非事务的方式运行,如果当前存在事务,暂停当前的事务。
    Propagation.NEVER以非事务的方式运行,如果当前存在事务,则抛出异常。
    Propagation.NESTED嵌套事务。

事务的隔离级别:isolation

  • 这就是我们常说的事务的隔离级别,默认值:Isolation.DEFAULT

  • 属性解释
    Isolation.DEFAULT使用底层数据库默认的隔离级别
    Isolation.READ_UNCOMMITTED读未提交
    Isolation.READ_COMMITTED读已提交
    Isolation.REPEATABLE_READ可重复读
    Isolation.SERIALIZABLE串行化

事务的超时时间:timeout

  • timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

事务的回滚类型:rollbackFor

  • rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

@Transactional 失效场景

同一个类中方法调用,注解失效

  • 场景重现:当调用 saveCabinet 方法时,save 方法中出异常不会回滚,如下代码

  • @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private CabinetMapper cabinetMapper;
        @Override
        public void saveCabinet(Cabinet cabinet) {
            save(cabinet);
        }
        @Transactional(rollbackFor = Exception.class)
        public void save(Cabinet cabinet) {
            cabinetMapper.insert(cabinet);
            int i = 1/0;	// 模拟出错
        }
    }
    
  • 原因分析:spring 中采用动态代理来实现事务,当同一个类调用 save 时,不是代理类在调用,所以扫描不到事务注解

  • 解决方案1:将 save 方法提到另一个类中,saveCabinet 调用另一个类的 save 方法(推荐使用)

  • // 当前类
    @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private MethodTest methodTest;
        @Override
        public void saveCabinet(Cabinet cabinet) {
            methodTest.save(cabinet);
        }
    }
    // 另一个类
    @Service
    public class MethodTest {
        @Autowired
        private CabinetMapper cabinetMapper;
        
        @Transactional(rollbackFor = Exception.class)
        public void save(Cabinet cabinet) {
            cabinetMapper.insert(cabinet);
            int i = 1/0; 	// 模拟出错
        }
    }
    
  • 解决方案2:在 CabinetServiceImpl 中自己注入自己,用自己的代理类调用 save 方法,即 cabinetServiceImpl.save()

  • @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private CabinetMapper cabinetMapper;
        
        @Autowired
        private CabinetServiceImpl cabinetServiceImpl;
        
        @Override
        public void saveCabinet(Cabinet cabinet) {
            cabinetServiceImpl.save(cabinet);
        }
        
        @Transactional(rollbackFor = Exception.class)
        public void save(Cabinet cabinet) {
            cabinetMapper.insert(cabinet);
            int i = 1/0;	// 模拟出错
        }
    }
    

异常被 catch “吃了”,注解失效

  • 场景重现:当调用 saveCabinet 方法时,方法体被 try-catch 了,不会回滚,如下代码

  • @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private CabinetMapper cabinetMapper;
        
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveCabinet(Cabinet cabinet) {
            try {
                cabinetMapper.insert(cabinet);
                int i = 1/0;	// 模拟出错
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  • 原因分析:异常直接捕获没有向上抛出,而是自己处理了,注解捕获不到异常,所以失效

  • 解决方案:将 catch 到的异常向上抛出 throw new Exception(e);,异常交给注解处理

  • @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private CabinetMapper cabinetMapper;
        
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveCabinet(Cabinet cabinet) throws Exception {
            try {
                cabinetMapper.insert(cabinet);
                int i = 1/0;	// 模拟出错
            } catch (Exception e) {
                e.printStackTrace();
                throw new Exception(e);
            }
        }
    }
    

@Transactional 应用在非 public 修饰的方法上

  • 场景重现:当调用的方法不是 public 修饰时,不会回滚,事务失效
  • 原因分析:在 spring aop 代理时,事务拦截器会在目标方法的执行前后进行拦截,其中的方法会去获取 Transactional 注解的一些信息,而其中的方法会去检查目标方法是否被 public 修饰,不是的话获取不到注解信息
  • 解决方案:将调用的方法访问修饰符改为 public

@Transactional 注解属性 rollbackFor 设置错误

  • 场景重现:当注解没有指定 rollbackFor 属性时,如下若抛出 FileNotFoundException 异常则不会回滚

  • @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private CabinetMapper cabinetMapper;
        
        @Override
        @Transactional
        public void saveCabinet(Cabinet cabinet) throws IOException {
            cabinetMapper.insert(cabinet);
            // 模拟出错
            throw new FileNotFoundException();
        }
    }
    
  • 原因分析:spring 默认抛出未检查异常,继承 RuntimeException 或者 Error 才会回滚,其他异常如 FileNotFoundException 则不会回滚

  • 解决方案:指定 rollbackFor 属性,一般指定 @Transactional(rollbackFor = Exception.class) 即可,也可以指定自定义异常

  • @Service
    public class CabinetServiceImpl implements CabinetService {
        @Autowired
        private CabinetMapper cabinetMapper;
        
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveCabinet(Cabinet cabinet) throws IOException {
            cabinetMapper.insert(cabinet);
            // 模拟出错
            throw new FileNotFoundException();
        }
    }
    

@Transactional 注解属性 propagation 设置错误

  • 使用了不支持事务的传播属性

数据库引擎不支持事务

  • 数据库不支持事务
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT枫斗者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值