天天看點

SpringBoot事務不起作用問題的解決方案

背景

在做業務開發時,遇到了一個事務不起作用的問題。大概流程是這樣的,方法内部的方法調用了一個帶事務的方法,失敗後事務沒有復原。查閱資料後,問題得到解決,記錄下來分享給大家。

場景

在一個UserService裡面,一個内部方法callSaveUser調用該service裡面的saveUser方法 

@Override
  @Transactional(rollbackFor = RuntimeException.class)
  public void saveUser(User user) {
    userMapper.save(user);
    throw new RuntimeException("");
  }
 
  /**
   * 内部調用儲存使用者方法
   *
   * @param user
   */
  @Override
  public void callSaveUser(User user) {
    this.saveUser(user);
  }
           

原因

AOP使用的是動态代理的機制,它會給類生成一個代理類,事務的相關操作都在代理類上完成。内部方式使用this調用方式時,使用的是執行個體調用,并沒有通過代理類調用方法,是以會導緻事務失效。

過程如下圖:

SpringBoot事務不起作用問題的解決方案

首先調用的是AOP代理對象而不是目标對象,首先執行事務切面,事務切面内部通過TransactionInterceptor環繞增強進行事務的增強,即進入目标方法之前開啟事務,退出目标方法時送出/復原事務。

解決辦法

1. 通過初始化方法在目标對象中注入代理對象

注入自身bean

@Autowired
@Lazy
private UserService service;
修改方法調用

/**
   * 解決方法一 在bean中将自己注入進來
   * @param user
   */
  @Override
  public void callSaveUser(User user) {
    this.service.saveUser(user);
  }
           

總結:隻能解決 普通(無循環依賴)的 的Bean注入AOP代理,無法解決循環依賴的AOP代理對象注入問題,即無法解決目标對象的自我調用問題。

2. 通過ApplicationContext引入bean

注入ApplicationContext

@Autowired
ApplicationContext applicationContext;
修改方法調用

/**
   * 解決方法二 通過applicationContext擷取到bean
   * @param user
   */
  @Override
  public void callSaveUser(User user) {
    ((UserService)applicationContext.getBean("userService")).saveUser(user);
  }
           

總結:能解決普通(無循環依賴)的AOP代理對象注入問題,而且也能解決循環依賴(應該是singleton之間的循環依賴)造成的目标對象無法注入AOP代理對象問題,但該解決方案不适合解決循環依賴中包含prototype Bean的自我調用問題。

3. 通過AopContext擷取目前類的代理類(推薦)

通過AopContext擷取目前類的代理類,直接通過代理類調用方法在引導類上添加@EnableAspectJAutoProxy(exposeProxy=true)注解,然後修改callSaveUser方法

/**
   * 解決方法三 通過applicationContext擷取到bean
   *
   * @param user
   */
  @Override
  public void invokeInsertUser(User user) {
    ((UserService) AopContext.currentProxy()).callSaveUser(user);
  }
           

原理:

  1. 在進入代理對象之後通過AopContext.serCurrentProxy(proxy)暴露目前代理對象ThreadLocal,并儲存上次ThreadLocal綁定的代理對象為oldProxy;
  2. 接下來我們可以通過 AopContext.currentProxy() 擷取目前代理對象;
  3. 在退出代理對象之前要重新将ThreadLocal綁定的代理對象設定為上一次的代理對象,即AopContext.serCurrentProxy(oldProxy)。

有些人不喜歡這種方式,說通過ThreadLocal暴露有性能問題,其實這個不需要考慮,因為事務相關的(Session和Connection)内部也是通過SessionHolder和ConnectionHolder暴露ThreadLocal實作的。

繼續閱讀