关于@Transactional注解失效的几种场景
前言
声明式事务注解@Transactional 是我们在开发过程当中碰到需要处理事务问题时经常使用到的注解,但在使用时需要注意在某些场景下@Transactional 可能会失效,网上搜索过一些但是感觉说的不全面,为此希望将@Transactional 事务失效的场景详细罗列以供大家参考
首先是@Transactional 事务生效的情况
/**
* 加入事务、不调用其他方法(事务生效)
*/
@Transactional
public void addMpUserToDB1() {
MpUser user = getMpUser(1);
mpUserMapper.insert(user);
}
/**
* 加入事务、最后抛出【运行时异常】(事务生效:出现异常会回滚)
*/
@Transactional
public void addMpUserToDB2() {
MpUser user = getMpUser(2);
this.mpUserMapper.insert(user);
int i = 1 / 0;
}
/**
* 当前类有事务方法调用当前类的事务方法(事务生效,下游的事务方法中出现异常会进行全面回滚)
*/
@Transactional
public void addMpUserToDB10() {
MpUser user = getMpUser(10);
this.mpUserMapper.insert(user);
addMpUserToDB3();
}
事务失效场景一:异常被try catch捕获
/**
* 【事务失效场景1 : 异常被try catch捕获】
* 加入事务、最后抛出【运行时异常】、自行try catch
* (事务失效1:异常会被try catch吞掉,不会进行回滚)
*/
@Transactional
public void addMpUserToDB3() {
try {
MpUser user = getMpUser(3);
this.mpUserMapper.insert(user);
int i = 1 / 0;
} catch (Exception e) {
log.error("保存用户信息出错:{}", e);
}
}
事务失效场景二、抛出的异常没有在 rollbackFor中指定
/**
* 【事务失效场景2:抛出的异常没有在 rollbackFor中指定】
* 加入事务、最后抛出【检查型异常】(事务失效2:出现异常不会回滚)
* 注意:手动指定rollback = ParseException.class后会进行回滚
*/
@Transactional //(rollbackFor = {ParseException.class})
public void addMpUserToDB4() throws ParseException {
MpUser user = getMpUser(4);
mpUserMapper.insert(user);
new SimpleDateFormat("yyyy-MM-dd").parse("123123");
}
事务失效场景三:本类无事务方法调用本类有事务方法
/**
* 【事务失效场景3:本类无事务方法调用本类有事务方法】
* 当前方法无事务,使用this调用当前类的事务方法(事务失效3:事务方法中出现异常不会回滚)
* 原理:发生自调用,类里面使用this调用本类的方法,此时这个this不是代理对象,所以事务失效
*/
public void addMpUserToDB6() {
addMpUserToDB3();
}
这种情况,可以通过使用Spring代理对象调用方法来使事务生效:
/**
* 当前方法无事务,使用spring容器中对象引用调用当前类的事务方法(事务生效:事务方法中出现异常会回滚)
* 原理:发生自调用,类里面使用容器中的代理类调用的事务方法,所以事务生效
*/
public void addMpUserToDB7() {
springTransactionDemoService.addMpUserToDB3();
}
/**
* 当前方法无事务,调用其他类的事务方法(事务生效:事务方法中出现异常会回滚)
* 原理:发生自调用,类里面使用容器中其他类的代理对象调用的事务方法,所以事务生效
*/
public void addMpUserToDB8() {
springTransactionDemoService2.addMpUserToDB8();
}
/**
* 使用当前类的代理对象调用事务方法(事务生效)
*/
public void addMpUserToDB9() {
((SpringTransactionDemoService) AopContext.currentProxy()).addMpUserToDB3();
}
事务失效场景四、事务方法被非public修饰
/**
* 【事务失效场景4:事务方法被非public修饰】
*/
@Transactional
void addMpUserToDB11() {
MpUser user = getMpUser(11);
this.mpUserMapper.insert(user);
int i = 1 / 0;
}
事务失效场景五、事务方法被final修饰
/**
* 【事务失效场景5:事务方法被final修饰】
* 被final修饰的方法,代理类中是获取不到成员属性的
*/
@Transactional
public final void addMpUserToDB12() {
MpUser user = getMpUser(12);
this.mpUserMapper.insert(user);
}
事务失效场景六、设置了错误的传播行为
/**
* 【事务失效场景6:设置了错误的传播行为】
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addMpUserToDB13() {
MpUser user = getMpUser(13);
this.mpUserMapper.insert(user);
int i = 1 / 0;
}
public static MpUser getMpUser(Integer num) {
MpUser user = new MpUser();
user.setName("事务测试用户" + num);
return user;
}
事务失效场景七、多线程情况下@Transactional事务失效
主要原因:
1、事务管理范围不正确:@Transactional注解仅对当前方法有效,如果在方法内创建新的线程或使用线程池等异步操作,该方法之外的代码将无法受到事务的管理。因此,在使用多线程进行批量操作时,需要确保整个批量操作处于同一事务管理范围内。
2、Spring事务和Java线程池机制的互动问题:在使用ThreadPoolExecutor进行批量操作时,线程池中的线程和Spring管理的事务并不是同一个线程,这可能会导致事务管理器感知不到线程中的异常,从而导致事务未能回滚。
最后
Spring声明式事务底层还是对数据库事务的支持和增强,事务隔离级别也以Spring事务设置的为准,当使用的数据库不支持事务,如mysql的存储引擎由 Innodb切换为 myisam,@Transaction注解自然也就失效了