@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
先看业务正常情况
用户1 扣 20 用户 2 加 20 没有问题
如果用户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); } }
一切正常 如我们所愿事务回滚了 但是在实际开发中,可能会遇到非常多非常多的坑 导致事务不生效
下面一起来看下那些情况会导致事务失效
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); }
什么情况 事务居然没回滚 ?
原因
来看看官方解释
/** * 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); }
再来测试一下
可以看到事务回滚了
二.异常被吃了
异常被吃了? 什么意思? 看下面这个代码
@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 () { } 包裹了
来,测试下看下是不是能回滚
结果异常没有回滚
原因
异常被方法内部处理了
解决办法
被事务管理的方法有异常直接抛出去 不要用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(); }
测试 非同一个类中非事务管理的方法调用被事务管理的方法生效了
那如果同一个类中会怎么样?
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(); }
来看结果
事务没有生效 什么情况??
原因
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); } }
注意 自动装配了 本类对象 也就是动态代理生成的代理类
能有效果吗?
看演示
事务生效了
重点: Spring 采用动态代理(AOP)生成代理对象,只有在代理对象之间进行调用时,才可以触发切面逻辑。而在同一个类中,方法B调用A,调用的是原对象的方法,而不是通过代理对象,也就保证事务性。
⭐️秋日的晚霞⭐️ ⭐️玲珑骰子安红豆 入骨相思知不知⭐️ 💋 如果对你有帮助,给博主一个免费的点赞 💋 👋 博客主页🎉 秋日的晚霞 ⭐️ 更多文章 请关注主页 📝 👋 一起走上java学习之路! 🎄 欢迎各位🔎点赞👍评论收藏⭐️ 🎄