Bootstrap

JAVA 事务不生效的常见场景和修改方案

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) 点我了解更多.

  1. REQUIRED:支持当前事务,若是当前没有事务,就新建一个事务。这是最多见的选择。(默认选择)

  2. SUPPORTS:支持当前事务,若是当前没有事务,就以非事务方式执行。 (不会回滚)

  3. MANDATORY:支持当前事务,若是当前没有事务,就抛出异常。 (很少用)

  4. REQUIRES_NEW:新建事务,若是当前存在事务,把当前事务挂起。 (新建一个独立的事务,上层事务调用只有,上层事务出现问题正常回滚,该独立事务不会回滚

  5. NOT_SUPPORTED:以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。 (无事务)

  6. NEVER:以非事务方式执行,若是当前存在事务,则抛出异常。 (无事务)

  7. 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();
		        }
}

注:事务失效的场景还有很多,此文只是列举了常见的场景

;