前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >springboot事务-失效的情况

springboot事务-失效的情况

作者头像
阿珍
发布2025-02-27 15:43:49
发布2025-02-27 15:43:49
3900
代码可运行
举报
运行总次数:0
代码可运行

经常遇到的事务失效情况

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

同类中方法间调用方式

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

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

代码语言:javascript
代码运行次数:0
复制
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. 本类中注入自己

代码语言:javascript
代码运行次数:0
复制
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的那个对象就可以了。于是就有了如下代码:

代码语言:javascript
代码运行次数:0
复制
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获取代理类

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

代码语言:javascript
代码运行次数:0
复制
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新特性,写一个事务的handler

代码语言:javascript
代码运行次数:0
复制
java 代码解读复制代码#新加一个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代理实现,通过如下代码来区分:

代码语言:javascript
代码运行次数:0
复制
java 代码解读复制代码//是否代理对象
AopUtils.isAopProxy(AopContext.currentProxy());
//是否cglib 代理对象
AopUtils.isCglibProxy(AopContext.currentProxy());
//是否dk动态代理
AopUtils.isJdkDynamicProxy(AopContext.currentProxy());

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 经常遇到的事务失效情况
  • 同类中方法间调用方式
    • 1.在同一个类中方法间调用方式不恰当,造成事务失效。
    • 2.why
  • 解决同类中方法间调用事务不起作用的方式
    • 1. 两个方法都有事务
    • 2. 把两个方法分在不同的service中
    • 3. 本类中注入自己
    • 4. 通过ApplicationContext获得代理类
    • 5. 通过AopContext获取代理类
    • 6. 利用JDK8新特性,写一个事务的handler
  • 补充
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档