applogo.png
简介

前言

日常开发中,我们经常使用Spring事务,肯定也或多或少的遇到过@Transactional失效的场景,熟悉的小伙伴可能很快的就能找到原因,但是不是很了解的小伙伴可能就要费些功夫排查原因了。

那么@Transactional失效的场景都有哪些呢?本章节针对@Transactional的问题,做以下总结整理。

1、同一个类中,方法内部调用事务失效

2、事务方法被final、static修饰

3、当前类没有被Spring管理

4、非public修饰的方法(存在版本差异)

5、事务多线程调用

6、数据库本身不支持事务

7、异常被方法内部try catch捕获,没有重新抛出

8、嵌套事务回滚多了

9、rollbackFor属性设置错误

10、设置不支持事务的传播机制

以上我们列举了10种场景,接下来我们针对不同的场景来具体的分析下。

 

 

一、代理不生效导致

1、同一个类中,方法内部调用事务失效

场景一:同一个类中,addOrder()方法无事务,addOrder2()方法存在事务,addOrder()调用addOrder2()。

@Service
public class OrderServiceImpl {

@Autowired
private OrderMapper orderMapper;

public int addOrder(Double amount, String address) {
int order = addOrder2(amount, address);
return order;
}

@Transactional
public int addOrder2(Double amount, String address) {
int order = orderMapper.addOrder(amount, address);
int i = order / 0;
return order;
}
}
我们通过外部方法调用addOrder()方法,来完成数据库的插入,通过手动的设置异常order/0,来观察addOrder2()方法中的数据是否会正常回滚。

 

通过数据库的结果显示,数据正常入库了,证明了我们的事务并未生效。

场景二:同一个类中,addOrder()和addOrder2()都存在事务,addOrder()调用addOrder2()。

@Service
public class OrderServiceImpl {

@Autowired
private OrderMapper orderMapper;

@Transactional
public int addOrder(Double amount, String address) {
int order = addOrder2(amount, address);
return order;
}

@Transactional
public int addOrder2(Double amount, String address) {
int order = orderMapper.addOrder(amount, address);
int i = order / 0;
return order;
}
}
order/0 产生异常之后,通过数据库的结果显示,发现数据并未入库,说明事务生效了。

通过两种场景对比,我们发现并不是所有同一个类,方法的内部调用事务都会失效。

2、事务方法被final、static修饰
@Service
public class OrderServiceImpl {

@Autowired
private OrderMapper orderMapper;

@Transactional
public final int addOrder(Double amount, String address) {
int order = orderMapper.addOrder(amount, address);
int i = order / 0;
return order;
}
@Transactional
public int addOrder2(Double amount, String address) {
int order = orderMapper.addOrder(amount, address);
int i = order / 0;
return order;
}
}
效原因:CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰的方法,无法被子类重写。

当通过外部接口调用addOrder()方法时,我们代理类不是走DynamicAdvisedInterceptor拦截器,而是直接调用了目标方法。

3、当前类没有被Spring管理
例如:Service类中没有@Service注解

public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Transactional
public int addOrder(Double amount, String address) {
int order = orderMapper.addOrder(amount, address);
int i = order / 0;
return order;
}
}
从以上的两种场景分析,我们得知@Transactional事务生效的前提条件是需要代理类对目标方法的调用,才能触发事务处理,而代理类是在springBoot启动时创建bean的时候,处理的。如果我们的类没有@Service注解,就不会交给spring容器初始化处理,也就无法为目标类生成代理类。

二、框架或者底层不支持
4、非public修饰的方法(存在版本差异)
@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Transactional
protected int addOrder(Double amount, String address) {
int order = orderMapper.addOrder(amount, address);
int i = order / 0;
return order;
}
}
通过外部接口的调用,异常之后,发现事务正常回滚,数据库数据并没有入库,说明事务是生效的。

springframework的版本:6.0.11

不是说非public修饰的方法,事务不生效吗?

通过本地debug分析源码得知,在spring版本6.0.11中是支持proected修饰的方法的。

 

5、事务多线程调用


@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Autowired
private SysUserServiceImpl sysUserService;

@Transactional(rollbackFor = Exception.class)
public int addOrder(Double amount, String address){
int order = orderMapper.addOrder(amount, address);
new Thread(() -> {
sysUserService.saveUser();
}).start();

return order;
}
}
@Service
public class SysUserServiceImpl {

@Transactional
public void saveUser(){
throw new RuntimeException("新增人员失败");
}
}
我们使用外部接口调用addOrder()方法时,sysUserService.saveUser()方法发生异常。但是发现orderMapper.addOrder()的数据正常入库了,事务失效。

 

通过本地debug和分析源码得知,我们的事务是给线程绑定的(TransactionAspectSupport.prepareTransactionInfo())。

 

————————————————

在执行sysUserService.saveUser()目标方法的时候,我们通过代理类执行逻辑,获取到的事务AbstractPlatformTransactionManager.getTransaction()其实是重新创建的一个事务。

 

此当saveUser()方法发生异常时,addOrder()方法的事务未能同步回滚数据。

6、数据库本身不支持事务
Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,Myisam存储引擎是不支持事务的,InnoDB引擎才支持事务。

这种问题出现的概率很小,在Mysql5之后,默认情况下是使用的InnoDB引擎存储

如果是历史项目,发现事务怎么配置都不生效,确认下你的存储引擎是否支持事务。

 

三、开发使用不当引发
7、异常被方法内部try catch捕获,没有重新抛出
@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;

@Transactional
public int addOrder(Double amount, String address) {
int order = 0;
try {
order = orderMapper.addOrder(amount, address);
int i = order / 0;
} catch (Exception e) {
}

return order;
}
}
当外部接口调用addOrder()方法,异常发生的时候,数据库数据正常入库了,事务未生效。

8、嵌套事务回滚多了

@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Autowired
private SysUserServiceImpl sysUserService;

@Transactional(rollbackFor = Exception.class)
public int addOrder(Double amount, String address){
int order = orderMapper.addOrder(amount, address);
sysUserService.saveUser();
return order;
}
}
@Service
public class SysUserServiceImpl {

@Transactional
public void saveUser(){
throw new RuntimeException("新增人员失败");
}
}
存在以上场景,即使sysUserService.saveUser()方法发生异常,我们期望orderMapper.addOrder()方法执行的结果也正常入库。

当出现嵌套事务发生异常的时候,实际上两个方法的事务都会进行回滚。

解决办法:

1、addOrder()方法使用trye/catch包住

2、saveUser()方法的事务传播机制调整为Propagation.REQUIRES_NEW

以上两种方式都可以实现我们需要的场景。
存在以上场景,即使sysUserService.saveUser()方法发生异常,我们期望orderMapper.addOrder()方法执行的结果也正常入库。

当出现嵌套事务发生异常的时候,实际上两个方法的事务都会进行回滚。

解决办法:

1、addOrder()方法使用trye/catch包住

2、saveUser()方法的事务传播机制调整为Propagation.REQUIRES_NEW

以上两种方式都可以实现我们需要的场景。

9、rollbackFor属性设置错误
@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;

@Transactional
public int addOrder(Double amount, String address) throws FileNotFoundException {
int order = orderMapper.addOrder(amount, address);
throw new FileNotFoundException("11111");
}
}
在demo中,我们手动的抛出FileNotFountException异常,这是一个IOException异常。

当我们使用@Transactional,在未对rollbackFor做配置的情况下,默认是支持对Runtime和Error异常的回滚的。

 

但当我们的demo中的异常是IOException的时候。

DefaultTransactionAttribute.rollbackOn()方法返回false,就会导致completeTransactionAfterThrowing()方法调用694行逻辑txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())提交事务。

 

从源码694行else逻辑的注释上我们也能看出,无法回滚异常。

所以通常情况下,我们建议指定@Transactional(rollbackFor = Exception.class)的方式进行异常捕获。

10、设置不支持事务的传播机制
Spring支持了7种传播机制,分别为:

 

上面不支持事务的传播机制为:PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER。

如果配置了这三种传播方式的话,在发生异常的时候,事务是不会回滚的。

@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;

@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public int addOrder(Double amount, String address) throws FileNotFoundException {
int order = orderMapper.addOrder(amount, address);
throw new FileNotFoundException("11111");
}
}
在处理事务异常回滚AbstractPlatformTransactionManager.processRollback()的逻辑中,这三种传播机制,就只是打印了下debug日志,没有进行真正的回滚,从日志记录信息中我们可以看到:

logger.debug("Should roll back transaction but cannot - no transaction available");

应该回滚但是不能,因为没有可用的事务。

 

Java中事务失效的几种场景:

 

1. 自调用
如果在同一个类中,一个事务方法调用了另一个非事务方法,事务可能会失效。例如:


收起

 

java


复制

public class MyService {

@Transactional
public void methodA() {
// 一些事务相关的操作
methodB();
}

public void methodB() {
// 非事务操作
}
}

在上述代码中,methodA 调用 methodB 时,methodB 的操作不会在 methodA 的事务范围内。


2. 异常处理不当
如果在事务方法中捕获了异常但没有进行回滚,或者捕获了非 RuntimeException 异常而没有进行回滚,事务可能失效。


收起

 

java


复制

@Transactional
public void myMethod() {
try {
// 事务操作
} catch (Exception e) {
// 没有进行回滚操作
}
}

3. 多线程环境
在多线程环境中,如果一个线程开始了事务,而另一个线程执行了相关操作,可能导致事务失效。


4. 数据库不支持事务
如果所使用的数据库引擎不支持事务,那么在 Java 中设置的事务也将无法生效。


5. 传播行为设置错误
事务的传播行为设置不正确,可能导致事务无法按照预期工作。例如,将传播行为设置为 PROPAGATION_NOT_SUPPORTED 时,会以非事务方式执行。


6. 数据源切换
如果在事务过程中切换了数据源,可能导致事务失效。


7. 手动提交或回滚
如果在事务方法中手动进行了提交或回滚操作,可能会导致 Spring 管理的事务失效。


例如,如果在事务方法中使用了 conn.commit() 来手动提交,就会破坏 Spring 对事务的管理。


这些场景在实际开发中需要特别注意,以确保事务能够正确地发挥作用,保证数据的一致性和完整性。

java 事务失效的12场景

以下是 12 个可能导致 Java 事务失效的场景:


1. 未添加事务注解
如果方法没有使用 @Transactional 注解进行标注,自然不会应用事务管理。


2. 方法访问权限问题
例如,事务方法是 private 的,可能导致事务无法生效。


3. 异常被吞掉


收起

 

java


复制

@Transactional
public void someMethod() {
try {
// 事务操作
} catch (Exception e) {
// 只是打印异常,没有抛出或回滚
System.out.println(e.getMessage());
}
}

4. 嵌套事务不当
在一个事务方法内部调用另一个事务方法,且传播行为设置不合理。


5. 数据库连接问题
如果数据库连接在事务执行过程中意外中断或出现异常,事务可能失效。


6. 事务超时
设置的事务超时时间过短,导致还未完成操作就超时结束事务。


7. 事务隔离级别设置错误
例如设置了不恰当的隔离级别,导致并发问题影响事务。


8. 调用外部非事务方法


收起

 

java


复制

@Transactional
public void transactionalMethod() {
externalNonTransactionalMethod();
// 外部方法的操作不在事务范围内
}

public void externalNonTransactionalMethod() {
// 操作
}

9. 不正确的事务传播行为
如将传播行为设置为 PROPAGATION_NEVER 时,强制不参与事务。


10. 多数据源场景
在涉及多个数据源的操作中,如果没有正确处理事务,可能导致事务失效。


11. 实体类未遵循规范
例如实体类的字段映射不正确,导致数据库操作异常,影响事务。


12. 框架配置错误
如 Spring 框架的事务配置有误,导致事务无法正常工作 

二维码

事务@Transactional失效的10种场景

保存图片,微信扫一扫

公众号:

上一页 下一页
其他信息
行业: 微营销
地区:
时间:2024-08-30
标签:

上一篇:java 事务失效的12场景

下一篇:如何解决接口幂等性问题?

赞 0
分享
猜你喜欢

账号登录,或者注册个账号?