Spring 相同类中@Transcational 相关学习

包括各种情况下的解决办法

Posted by Haiming on September 29, 2021

之前遇到了一些和@Transactional 相关的问题,刚好遇到一骗比较好的博文:https://medium.com/javarevisited/spring-transactional-mistakes-everyone-did-31418e5a6d6b,将其中的内容梳理一下。

1. 为什么在同类之中直接@Transactional 不生效

比如作者在文中给出的下面的例子:

public void registerAccount(Account acc) {
    createAccount(acc);

    notificationSrvc.sendVerificationEmail(acc);
}

@Transactional
public void createAccount(Account acc) {
    accRepo.save(acc);
    teamRepo.createPersonalTeam(acc);
}

这里面的 createAccount()上面的@Transactional,在上面的registerAccount()之中是不会生效的。原因在于这种 annotation,实际上 spring 是使用AOP 的方式进行的代理类,所以只有当一个 bean 被另一个 bean 调用的时候,这个过程才会发生。上面的调用很明显,就是在同一个类之中的调用,因此不会有任何的代理。

2. 如何解决@Transactional 在同类之中不生效

作者给出了三种方式:

  1. Self-inject:即在自身之中引用自身
  2. 创建另一层抽象
  3. registerAccount()之中用TransactionTemplate来包裹createAccount()的调用

第一种方式:

@Service
@RequiredArgsConstructor
public class AccountService {
    private final AccountRepository accRepo;
    private final TeamRepository teamRepo;
    private final NotificationService notificationSrvc;
    @Lazy private final AccountService self;

    public void registerAccount(Account acc) {
        self.createAccount(acc);

        notificationSrvc.sendVerificationEmail(acc);
    }

    @Transactional
    public void createAccount(Account acc) {
        accRepo.save(acc);
        teamRepo.createPersonalTeam(acc);
    }
}

可以看到其在自身之中使用@Lazy 来引用自己,并且使用

第三种方式

使用TransactionManager来做,同时 TransactionManager 之中也可以做不只一个的数据源的Transaction。

@Transactional
public void saveAccount(Account acc) {
    dataSource1Repo.save(acc);
    dataSource2Repo.save(acc);
}

可以使用 ChainedTransactionManager来做,这种方式可以 declear 多重数据源,并且如果有一场,会按照相反的顺序来进行 rollback。

但是,本身已经提交了的 commit 不会回滚!比如:

1st TX Platform: begin
  2nd TX Platform: begin
    3rd Tx Platform: begin
    3rd Tx Platform: commit
  2nd TX Platform: commit <-- fail
  2nd TX Platform: rollback  
1st TX Platform: rollback

这种情况之中,只有前两个操作会回滚,而第三个不会,因为其已经 commit 成功了。