Java业务开发最佳实践-Spring声明式事务

1,016 阅读2分钟

1. 确保事务配置生效

@Transactional 生效原则 1

@Transactional 注解只有标记在 public 方法上才能生效。

原因:Spring 默认事务通过动态代理的方式实现 AOP 对目标方法进行增强,因此只能对 public 方法进行增强。private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。

@Transactional 生效原则 2

必须通过代理过的类(Spring 注入的 Bean)从外部调用 @Transactional 注解标记的方法,事务才能生效。

原因:Spring 通过 AOP 技术对方法进行增强,要调用增强过的方法必然是调用代理后的对象。

2. 使用恰当的异常处理

通过 AOP 实现事务处理可以理解为,使用 try…catch… 包裹了标记 @Transactional 注解的方法,当方法出现了异常并且满足一定条件的时候,在 catch 里面可以设置事务回滚,没有异常则直接提交事务。

上述的“一定条件”,主要包括以下两点:

  1. 只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚。

    如果在事务方法中捕获了异常,则需要通过手动编码处理事务回滚:

    @Transactional
    public void createUserRight1(String name) {
        try {
            userRepository.save(new UserEntity(name));
            throw new RuntimeException("error");
        } catch (Exception ex) {
            log.error("create user failed", ex);
            // 手动处理事务回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
    
  2. 否则,默认设置为只有出现 RuntimeException(非受检异常)或 Error 时,事务才会回滚。

    如果希望 Spring 针对其他异常也可以回滚,则可以相应配置 @Transactional 注解的 rollbackFor 或 noRollbackFor 属性来覆盖其默认设置:

    // 遇到所有的 Exception 都回滚事务
    @Transactional(rollbackFor = Exception.class)
    public void createUserRight2(String name) throws IOException {
        // IOException 是受检异常
        userRepository.save(new UserEntity(name));
        otherTask();
    }
    

3. 复杂事务中细化配置事务传播方式

复杂事务场景:不在涉及多次数据库操作的业务逻辑中,希望将它们作为独立的事务进行提交或回滚。让主事务中包含独立的子事务,子事务的失败不会使得主事务回滚、不影响主流程。

细化配置事务传播方式:使用 @Transactional 注解的 Propagation 属性。为注解加上 propagation = Propagation.REQUIRES_NEW 来设置 REQUIRES_NEW 方式的事务传播策略,含义是执行到这个方法时需要开启新的事务,并挂起当前事务。

主事务方法:

@Transactional
public void createUserRight(UserEntity entity) {
    createMainUser(entity);
    try{
        subUserService.createSubUserWithExceptionRight(entity);
    } catch (Exception ex) {
        // 捕获异常,防止主方法回滚
        log.error("create sub user error:{}", ex.getMessage());
    }
}

子事务方法:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createSubUserWithExceptionRight(UserEntity entity) {
    log.info("createSubUserWithExceptionRight start");
    userRepository.save(entity);
    throw new RuntimeException("invalid status");
}