springboot事务-失效的情况

简介: 本文总结了常见的事务失效情况及解决方法,主要包括:1. **事务注解失效**:`@Transaction`必须作用于`public`方法,且需被Spring容器管理。2. **数据库引擎问题**:MyISAM不支持事务,应使用InnoDB。3. **异常处理不当**:异常被捕获未抛出或不在默认捕获范围内。4. **传播行为设置**:如设置为`Propagation.NOT_SUPPORTED`或`Propagation.REQUIRES_NEW`可能导致事务失效。5. **类内方法调用**:同一类中方法调用导致事务失效,需通过代理类或其他方式解决。

经常遇到的事务失效情况

  • 加@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新特性,写一个事务的handler

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代理实现,通过如下代码来区分:

java

代码解读

复制代码

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


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

相关文章
|
缓存 Java Spring
Spring框架(四) 三级缓存与循环依赖
首先我们需要明白什么是循环依赖 , 打个比方 , 就是说A对象在创建的过程中 , 需要依赖注入B对象 , 但是B对象没有 , 就需要去创建 , 而在创建B对象的过程中又需要注入A对象 , A对象此时还在创建中,所以就构成了一个死循环 , A,B相互依赖 这样的关系被成为循环依赖(当然 , 可能还会有其他的情况),下面我们就来看看Spring是如何让解决循环依赖的
515 0
|
9月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1246 0
|
5月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1010 0
|
Java
SpringBoot 内部方法调用,事务不起作用的原因及解决办法
在做业务开发时,遇到了一个事务不起作用的问题。大概流程是这样的,方法内部的定时任务调用了一个带事务的方法,失败后事务没有回滚。查阅资料后,问题得到解决,记录下来分享给大家。
673 4
|
6月前
|
人工智能 Java 数据库
如何保证接口幂等性?
在分布式系统中,接口幂等性至关重要。本文详解其定义、重要性及实现方案,包括唯一索引、Token机制、分布式锁、状态机与版本号机制,并提供最佳实践建议,助你提升系统可靠性与用户体验。
1115 1
|
7月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
466 10
|
JavaScript Java 关系型数据库
Spring事务失效的8种场景
本文总结了使用 @Transactional 注解时事务可能失效的几种情况,包括数据库引擎不支持事务、类未被 Spring 管理、方法非 public、自身调用、未配置事务管理器、设置为不支持事务、异常未抛出及异常类型不匹配等。针对这些情况,文章提供了相应的解决建议,帮助开发者排查和解决事务不生效的问题。
2145 1
|
9月前
|
JavaScript Java 开发者
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
396 7
|
9月前
|
Java 测试技术 Spring
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
317 3
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
896 13

热门文章

最新文章