Featured image of post 业务开发问题 代码篇(事务)

业务开发问题 代码篇(事务)

一、@Transactional 生效原则

  1. 除非特殊配置(比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效

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

  2. 必须通过代理过的类从外部调用目标方法才能生效。

    在service中未使用@Transaction注解声明的方法中去调用使用了@Transaction的方法,因为默认调用是使用this来调用的,所以该调用是由代理的类的内部进行调用的,所以事务不会再被触发。

二、事务即便生效也不一定能回滚

当方法出现了异常并且满足一定条件的时候才能进行回滚

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

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    try {
        // This is an around advice: Invoke the next interceptor in the chain.
    	// This will normally result in a target object being invoked.
    	retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
    	// target invocation exception
    	completeTransactionAfterThrowing(txInfo, ex);
    	throw ex;
    }
    finally {
    	cleanupTransactionInfo(txInfo);
    }
    

    在 Spring 的 TransactionAspectSupport 里有个 invokeWithinTransaction 方法,里面就是处理事 务的逻辑。可以看到,只有捕获到异常才能进行后续事务处理

  2. 默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring 才会回滚事务。

三、请确认事务传播配置是否符合自己的业务逻辑

场景:一个用户注册的操作,会插入一个主用户到用户表,还会注册一个关联的 子用户。我们希望将子用户注册的数据库操作作为一个独立事务来处理,即使失败也不会影 响主流程,即不影响主用户的注册。

先上原始代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private SubUserService subUserService;

    @Transactional
    public void createUserInfo(UserDTO userDTO){
        User user = userDTO.getUser();
        UserDetail userDetail = userDTO.getUserDetail();
        userRepository.save(user);
        userDetail.setUserId(user.getId());
        subUserService.createUserDetail(userDetail);
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Service
public class SubUserService {
    @Autowired
    private SubUserRepository subUserRepository;

    @Transactional
    public void createUserDetail(UserDetail userDetail) {
        subUserRepository.save(userDetail);
        throw new RuntimeException("save error ......");
    }
}

看到这部分代码,当我们调用时会发现,不论是subUserRepository.save(userDetail) ,还是userRepository.save(user)都回滚了,这个是正常操作。如果要满足场景subUserRepository.save(userDetail)回滚,而userRepository.save(user)不会滚。我们根据前面回滚需要满足的条件中随便破坏一条不就完事了吗。所以我选择将subUserService.createUserDetail(userDetail)抛出的异常给捕获了不就完事了吗,然后走你。

1
2
3
4
5
6
try {
            userDetail.setUserId(user.getId());
            subUserService.createUserDetail(userDetail);
        } catch (Exception e) {
            System.out.println("not rollback this method.......");
        }

but 、but、but还是回滚了,这是怎么回事呢。

原因:@Transactional标注方法调用另一个@Transactional标注方法方法时,默认使用的事务传播机制是使用同一个事物,所以subUserRepository.save(userDetail)回滚时,因为userRepository.save(user)和它使用的是同一个事务,那么它没理由不回滚。

so …… 需要改一下事务的传播机制 , 即在你要调用的事务方法声明该事务是沿用调用方的事务还是自己新开一个事务

1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createUserDetail(UserDetail userDetail) {
    subUserRepository.save(userDetail);
    throw new RuntimeException("save error ......");
}
使用 Hugo 构建
主题 StackJimmy 设计

发布了 16 篇文章 | 共 31507 字