springboot事务-失效的情况

码农老张 后端 2025-02-27

springboot事务-失效的情况

经常遇到的事务失效情况

  • 加@Transaction批注的方法必须是public,否则失效。protected也不成。
  • 如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎。
  • 没有被Spring容器管理到,最常见的是没有在服务类上加@Service注解。
  • 异常被捕获,没有抛出来。
  • 异常不在spring默认捕获异常中。spring默认捕获不受控异常,这个在《springboot事务-使用的前提》介绍过。
  • 主动设置了传播方式为:Propagation.NOT_SUPPORTED。当然这种情况很少,只是一种可能性而已。
  • 主动设置了传播方式为:Propagation.REQUIRES_NEW。子事务和父事务没有关系。这种可能性也很小。
  • 在同一个类中方法间调用方式不恰当,造成事务失效。

同类中方法间调用方式

1.在同一个类中方法间调用方式不恰当,造成事务失效。

这点比较有意思,这里特别说明下,这里先举个例子说明这种情况:java

代码解读
复制代码
//保存父方法 public void saveParentMethod() { //插入parent StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("parent"); this.stockInfoMapper.insertSelective(stockInfo); //插入child this.saveChildStockInfo(); } //保存子方法 @Transactional(rollbackFor = Exception.class) public void saveChildStockInfo() { StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("child"); this.stockInfoMapper.insertSelective(stockInfo); //制造异常 int i = 1 / 0; }

这里首先可以看到saveParentMethod方法是没有事务的;而saveChildStockInfo却是有事务的。当测试类调用

saveParentMethod方法后,你会发现事务完全不起作用了。可能在我们的理解中:parent应该入库而child不应该

入库。然而实际情况是:child也入库了,明显是事务失效了。

2.why

spring基于AOP机制实现事务的管理。spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,若是包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,其实是由代理类来调用的,代理类在调用以前就会启动transaction。然而,若是这个有注解的方法是被同一个类中的其余方法调用的,那么该方法的调用并无经过代理类,而是直接经过原来的那个bean,因此就不会启动transaction,最后看到的现象就是@Transactional注解无效。

解决同类中方法间调用事务不起作用的方式

1. 两个方法都有事务

就拿开始的例子如果saveParentMethod上有@Transactional注解,自然就不会出现不起作用的情况了。

(感觉这个方法很没有营养的)

2. 把两个方法分在不同的service中

这个方法虽然也有点看着愚蠢,但是的确很多情况下很实用。尤其是如果你的servce叫XXService。那么分出的有

事务的方法可以放在XXServiceHelper类中。既能解决事务不起作用的问题,同样可以使你的主Service变的很清爽。所以这个方法,在某些情况下反而非常适合。

3. 本类中注入自己java

代码解读
复制代码
@Service public class OuterBean { @Resource private StockInfoMapper stockInfoMapper; //自己注入自己 @Resource private OuterBean outerBean; public void saveParentMethod() { //插入parent StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("parent"); this.stockInfoMapper.insertSelective(stockInfo); //插入child。这里相当于调用代理的方法 this.outerBean.saveChildStockInfo(); } @Transactional(rollbackFor = Exception.class) public void saveChildStockInfo() { StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("child"); this.stockInfoMapper.insertSelective(stockInfo); //制造异常 int i = 1 / 0; } }

可能有人会担心这样会有循环依赖的问题,事实上,spring通过三级缓存解决了循环依赖的问题,所以上面的写法不会有循环依赖问题。但是!!!,这不代表spring永远没有循环依赖的问题(@Async导致循环依赖了解下)

4. 通过ApplicationContext获得代理类

既然我们知道@Transactional是通过aop来实现的,这里就很容易想到--只要拿到代理我们Servcie的那个对象就可以了。于是就有了如下代码:java

代码解读
复制代码
@Service public class OuterBean { @Resource private StockInfoMapper stockInfoMapper; @Autowired private ApplicationContext applicationContext; public void saveParentMethod() { //插入parent StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("parent"); this.stockInfoMapper.insertSelective(stockInfo); //通过ApplicationContext获取代理对象 this.applicationContext.getBean(OuterBean.class).saveChildStockInfo(); } @Transactional(rollbackFor = Exception.class) public int saveChildStockInfo() { StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("child"); int insert = this.stockInfoMapper.insertSelective(stockInfo); //制造异常 int i = 1 / 0; return insert; } }

5. 通过AopContext获取代理类

和上面的方式原理差不多,只是获得代理类的方式不同。代码如下:java

代码解读
复制代码
#这里多加一个批注,不加会报错:Set 'exposeProxy' property on Advised to 'true' to make it available @EnableAspectJAutoProxy(proxyTargetClass=true, exposeProxy=true) @Service public class OuterBean { @Resource private StockInfoMapper stockInfoMapper; public void saveParentMethod() { //插入parent StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("parent"); this.stockInfoMapper.insertSelective(stockInfo); //通过AopContext调用saveChildStockInfo方法 ((OuterBean) AopContext.currentProxy()).saveParentMethod(); } @Transactional(rollbackFor = Exception.class) public void saveChildStockInfo() { StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("child"); this.stockInfoMapper.insertSelective(stockInfo); //制造异常 int i = 1 / 0; } }

此方法还是非常推荐的。使用简单而且不会有循环依赖的问题,非常的nice。

6. 利用JDK8新特性,写一个事务的handlerjava

代码解读
复制代码
#新加一个TransactionHandler类 @Service public class TransactionHandler { @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public <T> T runInTransaction(Supplier<T> supplier) { return supplier.get(); } @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public <T> T runInNewTransaction(Supplier<T> supplier) { return supplier.get(); } } #改造OuterBean @Service public class OuterBean { @Resource private StockInfoMapper stockInfoMapper; @Autowired private TransactionHandler transactionHandler; public void saveParentMethod() { //插入parent StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("parent"); this.stockInfoMapper.insertSelective(stockInfo); //通过TransactionHandler调用saveChildStockInfo方法 this.transactionHandler.runInTransaction(() -> saveChildStockInfo()); } public int saveChildStockInfo() { StockInfo stockInfo = new StockInfo(); stockInfo.setProductId("child"); int insert = this.stockInfoMapper.insertSelective(stockInfo); //制造异常 int i = 1 / 0; return insert; } }

这个方法看着有点麻烦。但是有几个优势是其他方式无法比拟的:

  • 它可以应用于私有方法。因此,您不必为了满足Spring的限制而通过公开方法来破坏封装。

  • 可以在不同的事务传播中调用相同的方法,并且由调用方选择合适的方法。比较以下两行:java

    代码解读
    复制代码
    #你可以指定传播性 this.transactionHandler.runInTransaction(() -> saveChildStockInfo()); this.transactionHandler.runInNewTransaction(() -> saveChildStockInfo());
  • 它是显式调用的,因此更具可读性。

补充

spring的aop代理有jdk代理和cglib代理实现,通过如下代码来区分:java

代码解读
复制代码
//是否代理对象 AopUtils.isAopProxy(AopContext.currentProxy()); //是否cglib 代理对象 AopUtils.isCglibProxy(AopContext.currentProxy()); //是否dk动态代理 AopUtils.isJdkDynamicProxy(AopContext.currentProxy());

转载来源:https://juejin.cn/post/7415751335490109478

Apipost 私有化火热进行中

评论