微信扫一扫,添加关注
前言 日常开发中,我们经常使用Spring事务,肯定也或多或少的遇到过@ ......
公众号:
联系QQ:
190
热度
其他信息
前言
日常开发中,我们经常使用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 框架的事务配置有误,导致事务无法正常工作