JAVA 事务不生效的常见场景和修改方案
java事务是很多初学者和初级乃至于中级程序员不明白的点,绝大多数情况都是使用声明式事务也就是在方法上面来个@Transactional(rollbackFor = Exception.class),会出现事务没有回滚的情况,很多博客都有写,这篇博客主要是整理和记录一下事务不生效的情况和处理方法,如有问题可以私信或评论
不生效的场景
场景1:本身没有交给spring 管理
//@Service 该注解未生效
public class TestService {
@Autowired
XXXXDao xxxxDao;
/**
* 操作数据库进行简单的添加
*/
@Transactional
public void addSomeThing(Stu stu){
xxxxDao.add(stu);
}
}
事务不生效的原因:上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢。
场景2:spring没有启用事务管理器(spring boot 默认开启的,只讨论spring)
@Configuration
public class XXXSystemConfig {
//配置事务管理器
//@Bean
//public PlatformTransactionManager transactionManager() {
// return new DataSourceTransactionManager(dataSource());
//}
}
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
/**
* 操作数据库进行简单的添加
*/
@Transactional
public void addSomeThing(Stu stu){
xxxxDao.add(stu);
}
}
事务不生效的原因:没有在AppConfig中配置事务管理器,因此Spring无法创建事务代理对象,导致事务不生效。即使在MyService中添加了@Transactional注解,该方法也不会被Spring管理的事务代理拦截。
场景3:自身调用情况,原因是因为java中的事务依耐与aop中的代理,而非public权限的是无法代理的,具体参考源码:AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法有个注释说明 no-public(事务方法不能被final、static关键字修饰)
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
/**
* 操作数据库进行简单的添加
*/
@Transactional
private void addSomeThing(Stu stu){
xxxxDao.add(stu);
}
}
所以场景1 只需要将 private -> public
注:同理大家应该不难推算出 final、static 等修饰的也不能被代理
场景4:方法内部调用情况,上面3中提到是依耐于aop中的代理,而这里自身调用是没有走代理的,导致回滚失败
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
/**
* 操作数据库进行简单的添加
*/
public void addSomeThing(Stu stu){
add(stu);
}
@Transactional
public void add(Stu stu) {
xxxxDao.add(stu);
int a = 1/0;
}
//上面的情况处理最简单粗暴的方式就是 注入自己然后调用(这里只是处理之一,有很多种方式
//但是核心就是要让他走代理例如:
//1、写个工具类继承ApplicationContextAware 然后getbean();
//2、挪另外一个servie,建议直接新增一个文件;
//3、AopContext.currentProxy())
@Autowired
TestService service;
public void addSomeThing(Stu stu){
service.add(stu);
}
//@Transactional
//如果此处的修饰词是private 会出现 xxxxDao = null,内部调用 且方法非公开 本质是new 了一个service进行方法调用
//private void add(Stu stu) {
// xxxxDao.add(stu);
// int a = 1/0;
//}
@Transactional
public void add(Stu stu) {
xxxxDao.add(stu);
int a = 1/0;
}
场景5:选择错误的事务传播特性(一般不写,使用默认的不会出现问题)(@Transactional注解时指定的propagation) 点我了解更多.
REQUIRED:支持当前事务,若是当前没有事务,就新建一个事务。这是最多见的选择。(默认选择)
SUPPORTS:支持当前事务,若是当前没有事务,就以非事务方式执行。 (不会回滚)
MANDATORY:支持当前事务,若是当前没有事务,就抛出异常。 (很少用)
REQUIRES_NEW:新建事务,若是当前存在事务,把当前事务挂起。 (新建一个独立的事务,上层事务调用只有,上层事务出现问题正常回滚,该独立事务不会回滚)
NOT_SUPPORTED:以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。 (无事务)
NEVER:以非事务方式执行,若是当前存在事务,则抛出异常。 (无事务)
NESTED:支持当前事务,若是当前事务存在,则执行一个嵌套事务,若是当前没有事务,就新建一个事务。 (该子事务只是一起提交,绝大数情况能和REQUIRES_NEW进行替换,所以回滚机制基本一致
场景6:错误的异常处理方式
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
/**
* 操作数据库进行简单的添加
*/
@Transactional
public void addSomeThing(Stu stu){
try {
xxxxDao.add(stu);
int a = 1/0;
}catch (Exception e){
//1.不做任何处理,不抛异常,事务不回滚
e.printStackTrace();
}
}
@Transactional
public void addSomeThing(Stu stu){
try {
xxxxDao.add(stu);
int a = 1/0;
}catch(Exception e) {
//2.即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。 异常被catch住,忘记抛出,记住必须抛异常才会回滚的.
// throw new Exception("dddd");
}
}
//这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class)
}
场景7:配置错误的 @Transactional 注解
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
/**
* 操作数据库进行简单的添加
*/
@Transactional(readOnly = true) readOnly=true属性指示这是一个只读事务
@Transactional(timeout = 1) timeout属性被设置为1秒,这意味着如果事务在1 秒内无法完成,则报事务超时了
public void addSomeThing(Stu stu){
xxxxDao.add(stu);
}
}
场景8:异常被捕获且没有再次抛出去
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
@Transactional()
public void addSomeThing(Stu stu){
try {
xxxxDao.add(stu);
} catch (Exception e) {
log.error("add error,id:{},message:{}", tianluo.getId(),e.getMessage());
// throw e;
}
}
场景9:手动抛出了其他的异常(Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚,除非,用rollbackFor属性指定配置)
@Service
public class TestService {
@Autowired
XXXXDao xxxxDao;
// @Transactional(rollbackFor = Exception.class)
@Transactional()
public void addSomeThing(Stu stu){
try {
xxxxDao.add(stu);
} catch (Exception e) {
log.error("add error,id:{},message:{}", tianluo.getId(),e.getMessage());
throw new Exception();
}
}
注:事务失效的场景还有很多,此文只是列举了常见的场景