文章目录
- 1、方法内部调用
- 2、修饰符
- 3、非运行时异常
- 4、try…catch捕获异常
- 5、多线程调用
- 6、同时使用@Transactional和@Async
- 7、错误使用事务传播行为
- 8、使用的数据库不支持事务
- 9、是否开启事务支持
在工作中,经常会碰到一些事务失效的坑,基于遇到的情况,以及了解到的坑,写了本篇文章与大家学习交流~
1、方法内部调用
非事务方法调用事务方法会出现问题
@Autowired
private KsAService ksAService;
public void add() {
this.doAdd();
}
@Transactional
public void doAdd() {
KsA ksA = new KsA();
ksA.setName("forlan");
ksAService.insert(ksA);
int res = 1 / 0;
}
原因:Spring声明式事务是基于动态代理(AOP)实现的对bean的管理和切片,为我们每个class生成代理对象,只有代理对象之间调用,才会触发切面逻辑,方法A调用方法B,这里的ksAService是真实对象,不是代理对象
解决:
- add方法加上注解@Transactional
- doAdd方法中使用代理对象来调用
@Transactional
public void doAdd() {
KsA ksA = new KsA();
ksA.setName("forlan");
KsAService ksAService =(KsAService) AopContext.currentProxy();
ksAService.insert(ksA);
int res = 1 / 0;
}
2、修饰符
@Transactional
private void doAdd() {
KsA ksA = new KsA();
ksA.setName("forlan");
ksAService.insert(ksA);
int res = 1 / 0;
}
原因:Spring声明式事务是基于动态代理实现
- 非public修饰,不能被代理
- static修饰的方法属于类,不属于对象,不能被重写,不能被代理
- final修饰的方法不能被重写,不能被代理
解决:不使用这些修饰符,非public、static、final
- static、final,代理里面一般有提示,Methods annotated with ‘@Transactional’ must be overridable
- 注意不要使用非public修饰,特别是private,我们很习惯性写成这个
3、非运行时异常
@Transactional
public void doAdd() throws IOException{
KsA ksA = new KsA();
ksA.setName("forlan");
ksAService.insert(ksA);
throw new IOException();
}
原因:使用Spring的@Transactiona开启事务,默认Error和RuntimeException及其子类才会回滚解决:指定异常回滚@Transactional(rollbackFor = Exception.class)
4、try…catch捕获异常
@Transactional
public void doAdd() throws RuntimeException {
KsA ksA = new KsA();
ksA.setName("forlan");
ksAService.insert(ksA);
try {
int res = 1 / 0;
} catch (Exception e) {
// 抛出异常
// 设置手动回滚
}
}
原因:自己捕获了异常,则事务无法感知
解决:
- 抛出异常:throw new RuntimeException(e.getMessage());
- 设置手动回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
5、多线程调用
@Transactional
public void doAdd() {
new Thread(() -> {
KsA ksA = new KsA();
ksA.setName("forlan");
ksAService.insert(ksA);
}).start();
int res = 1 / 0;
}
原因:因为Spring的事务是通过数据库连接来实现不同线程使用不同的数据库连接,放在ThreadLocal中,基于同一数据库连接的事务才能同时提交或回滚,多线程场景下,拿到的数据库连接是不一样的解决:
- 分布式事务保证
- 自己实现事务回滚
6、同时使用@Transactional和@Async
A类事务方法调用B类异步方法
public class A{
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("forlan");
ksAService.insert(ksA);
}
}
public class B{
@Async
public void insert(KsA ksA) {
this.ksADao.insert(ksA);
int res = 1 / 0;
}
}
原因:同多线程调用问题类似,异步方法属于开启一个新线程执行了解决:
- 异步方法加上@Transactional
7、错误使用事务传播行为
具体可以了解 Spring事务传播行为实战
比如:使用了@Transactional(propagation = Propagation.NOT_SUPPORTED),(不支持事务)如果当前存在事务,就把当前事务挂起
8、使用的数据库不支持事务
比如,MySQL中的MyISAM,是不支持事务的原因:Spring事务基于数据库事务实现
9、是否开启事务支持
我们使用的SpringBoot默认开启事务支持了,通过我们引入的依赖jar包,可以发现@EnableTransactionManagement