![](/img/trans.png)
[英]Could not commit JPA transaction; nested exception - Transaction marked as 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()
方法時發生的情況:
現在,如果ServiceUser.method()
不是事務性的,則會發生以下情況:
無法提交 JPA 事務:事務標記為 rollbackOnly
當您調用也標記為@Transactional
嵌套方法/服務時,會發生此異常。 JB Nizet 詳細解釋了該機制。 我想在它發生時添加一些場景以及一些避免它的方法。
假設我們有兩個 Spring 服務: Service1
和Service2
。 在我們的程序中,我們調用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)。
場景:
標記為例外回滾拋出交易method2
。 這是 JB Nizet 解釋的默認情況。
將method2
注釋為@Transactional(readOnly = true)
仍將事務標記為回滾(從method1
退出時拋出異常)。
將method1
和method2
注釋為@Transactional(readOnly = true)
仍將事務標記為回滾(從method1
退出時拋出異常)。
使用@Transactional(noRollbackFor = SomeException)
注釋method2
可防止將事務標記為回滾(從method1
退出時不會拋出異常)。
假設method2
屬於Service1
。 從method1
調用它不會通過 Spring 的代理,即 Spring 不知道從method2
拋出SomeException
。 在這種情況下,事務沒有標記為回滾。
假設method2
沒有用@Transactional
注釋。 從method1
調用它確實要經過 Spring 的代理,但是 Spring 並不關心拋出的異常。 在這種情況下,事務沒有標記為回滾。
用@Transactional(propagation = Propagation.REQUIRES_NEW)
注釋method2
使method2
開始新的事務。 第二個事務在從method2
退出時被標記為回滾,但在這種情況下原始事務不受影響(從method1
退出時不會拋出異常)。
如果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.