簡體   English   中英

無法提交 JPA 事務:事務標記為 rollbackOnly

[英]Could not commit JPA transaction: Transaction marked as rollbackOnly

我在我正在處理的應用程序之一中使用 Spring 和 Hibernate,但在處理事務時遇到了問題。

我有一個服務類,它從數據庫加載一些實體,修改它們的一些值,然后(當一切都有效時)將這些更改提交到數據庫。 如果新值無效(我只能在設置后檢查),我不想保留更改。 為了防止 Spring/Hibernate 保存更改,我在方法中拋出了一個異常。 然而,這會導致以下錯誤:

Could not commit JPA transaction: Transaction marked as rollbackOnly

這是服務:

@Service
class MyService {

  @Transactional(rollbackFor = MyCustomException.class)
  public void doSth() throws MyCustomException {
    //load entities from database
    //modify some of their values
    //check if they are valid
    if(invalid) { //if they arent valid, throw an exception
      throw new MyCustomException();
    }

  }
}

這就是我調用它的方式:

class ServiceUser {
  @Autowired
  private MyService myService;

  public void method() {
    try {
      myService.doSth();
    } catch (MyCustomException e) {
      // ...
    }        
  }
}

我希望發生的事情:數據庫沒有更改,用戶也沒有看到異常。

發生了什么:數據庫沒有變化,但應用程序崩潰了:

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly

它正確地將事務設置為 rollbackOnly 但為什么回滾會因異常而崩潰?

我的猜測是ServiceUser.method()本身就是事務性的。 不應該。 這就是原因。

以下是調用ServiceUser.method()方法時發生的情況:

  1. 事務攔截器攔截方法調用,並啟動一個事務,因為沒有事務已經處於活動狀態
  2. 該方法被稱為
  3. 該方法調用 MyService.doSth()
  4. 事務攔截器攔截方法調用,看到一個事務已經處於活動狀態,不做任何事情
  5. doSth() 被執行並拋出異常
  6. 事務攔截器攔截異常,將事務標記為rollbackOnly,並傳播異常
  7. ServiceUser.method() 捕獲異常並返回
  8. 事務攔截器,因為它已經啟動了事務,嘗試提交它。 但是Hibernate拒絕這樣做,因為事務被標記為rollbackOnly,所以Hibernate拋出異常。 事務攔截器通過拋出一個包含休眠異常的異常來向調用者發出信號。

現在,如果ServiceUser.method()不是事務性的,則會發生以下情況:

  1. 該方法被稱為
  2. 該方法調用 MyService.doSth()
  3. 事務攔截器攔截方法調用,看到沒有事務已經處於活動狀態,從而啟動一個事務
  4. doSth() 被執行並拋出異常
  5. 事務攔截器攔截異常。 由於它已經啟動了事務,並且由於拋出了異常,它回滾事務,並傳播異常
  6. ServiceUser.method() 捕獲異常並返回

無法提交 JPA 事務:事務標記為 rollbackOnly

當您調用也標記為@Transactional嵌套方法/服務時,會發生此異常 JB Nizet 詳細解釋了該機制。 我想在它發生時添加一些場景以及一些避免它的方法

假設我們有兩個 Spring 服務: Service1Service2 在我們的程序中,我們調用Service1.method1() ,后者又調用Service2.method2()

class Service1 {
    @Transactional
    public void method1() {
        try {
            ...
            service2.method2();
            ...
        } catch (Exception e) {
            ...
        }
    }
}

class Service2 {
    @Transactional
    public void method2() {
        ...
        throw new SomeException();
        ...
    }
}

除非另有說明,否則SomeException是未經檢查的(擴展 RuntimeException)。

場景:

  1. 標記為例外回滾拋出交易method2 這是 JB Nizet 解釋的默認情況。

  2. method2注釋為@Transactional(readOnly = true)仍將事務標記為回滾(從method1退出時拋出異常)。

  3. method1method2注釋為@Transactional(readOnly = true)仍將事務標記為回滾(從method1退出時拋出異常)。

  4. 使用@Transactional(noRollbackFor = SomeException)注釋method2可防止將事務標記為回滾(從method1退出時不會拋出異常)。

  5. 假設method2屬於Service1 method1調用它不會通過 Spring 的代理,即 Spring 不知道從method2拋出SomeException 在這種情況下,事務沒有標記為回滾

  6. 假設method2沒有用@Transactional注釋。 method1調用它確實要經過 Spring 的代理,但是 Spring 並不關心拋出的異常。 在這種情況下,事務沒有標記為回滾

  7. @Transactional(propagation = Propagation.REQUIRES_NEW)注釋method2使method2開始新的事務。 第二個事務在從method2退出時被標記為回滾,但在這種情況下原始事務不受影響(從method1退出時不會拋出異常)。

  8. 如果SomeException檢查(不擴展 RuntimeException),Spring 默認情況下在攔截檢查異常時不標記事務回滾(從method1退出時拋出異常)。

查看本要點中測試的所有場景。

對於那些不能(或不想)設置調試器來跟蹤導致回滾標志設置的原始異常的人,您可以在整個代碼中添加一堆調試語句來查找行觸發僅回滾標志的代碼:

logger.debug("Is rollbackOnly: " + TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());

在整個代碼中添加這個允許我縮小根本原因,通過對調試語句進行編號並查看上述方法從返回“false”到“true”的位置。

首先保存子對象,然后調用最終存儲庫保存方法。

@PostMapping("/save")
    public String save(@ModelAttribute("shortcode") @Valid Shortcode shortcode, BindingResult result) {
        Shortcode existingShortcode = shortcodeService.findByShortcode(shortcode.getShortcode());
        if (existingShortcode != null) {
            result.rejectValue(shortcode.getShortcode(), "This shortode is already created.");
        }
        if (result.hasErrors()) {
            return "redirect:/shortcode/create";
        }
        **shortcode.setUser(userService.findByUsername(shortcode.getUser().getUsername()));**
        shortcodeService.save(shortcode);
        return "redirect:/shortcode/create?success";
    }

正如@Yaroslav Stavnichiy 所解釋的,如果服務被標記為事務性 spring 會嘗試自行處理事務。 如果發生任何異常,則執行回滾操作。 如果在您的場景中 ServiceUser.method() 沒有執行任何事務操作,您可以使用 @Transactional.TxType 注釋。 'NEVER' 選項用於在事務上下文之外管理該方法。

Transactional.TxType 參考文檔在這里

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM