[英]Could not commit JPA transaction: Transaction marked as rollbackOnly
I'm using Spring and Hibernate in one of the applications that I'm working on and I've got a problem with handling of transactions.我在我正在处理的应用程序之一中使用 Spring 和 Hibernate,但在处理事务时遇到了问题。
I've got a service class that loads some entities from the database, modifies some of their values and then (when everything is valid) commits these changes to the database.我有一个服务类,它从数据库加载一些实体,修改它们的一些值,然后(当一切都有效时)将这些更改提交到数据库。 If the new values are invalid (which I can only check after setting them) I do not want to persist the changes.
如果新值无效(我只能在设置后检查),我不想保留更改。 To prevent Spring/Hibernate from saving the changes I throw an exception in the method.
为了防止 Spring/Hibernate 保存更改,我在方法中抛出了一个异常。 This however results in the following error:
然而,这会导致以下错误:
Could not commit JPA transaction: Transaction marked as rollbackOnly
And this is the service:这是服务:
@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();
}
}
}
And this is how I invoke it:这就是我调用它的方式:
class ServiceUser {
@Autowired
private MyService myService;
public void method() {
try {
myService.doSth();
} catch (MyCustomException e) {
// ...
}
}
}
What I'd expect to happen: No changes to the database and no exception visible to the user.我希望发生的事情:数据库没有更改,用户也没有看到异常。
What happens: No changes to the database but the app crashes with:发生了什么:数据库没有变化,但应用程序崩溃了:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
It's correctly setting the transaction to rollbackOnly but why is the rollback crashing with an exception?它正确地将事务设置为 rollbackOnly 但为什么回滚会因异常而崩溃?
My guess is that ServiceUser.method()
is itself transactional.我的猜测是
ServiceUser.method()
本身就是事务性的。 It shouldn't be.不应该。 Here's the reason why.
这就是原因。
Here's what happens when a call is made to your ServiceUser.method()
method:以下是调用
ServiceUser.method()
方法时发生的情况:
Now if ServiceUser.method()
is not transactional, here's what happens:现在,如果
ServiceUser.method()
不是事务性的,则会发生以下情况:
Could not commit JPA transaction: Transaction marked as rollbackOnly
无法提交 JPA 事务:事务标记为 rollbackOnly
This exception occurs when you invoke nested methods/services also marked as @Transactional
.当您调用也标记为
@Transactional
嵌套方法/服务时,会发生此异常。 JB Nizet explained the mechanism in detail. JB Nizet 详细解释了该机制。 I'd like to add some scenarios when it happens as well as some ways to avoid it .
我想在它发生时添加一些场景以及一些避免它的方法。
Suppose we have two Spring services: Service1
and Service2
.假设我们有两个 Spring 服务:
Service1
和Service2
。 From our program we call Service1.method1()
which in turn calls Service2.method2()
:在我们的程序中,我们调用
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
is unchecked (extends RuntimeException) unless stated otherwise.除非另有说明,否则
SomeException
是未经检查的(扩展 RuntimeException)。
Scenarios:场景:
Transaction marked for rollback by exception thrown out of method2
.标记为例外回滚抛出交易
method2
。 This is our default case explained by JB Nizet.这是 JB Nizet 解释的默认情况。
Annotating method2
as @Transactional(readOnly = true)
still marks transaction for rollback (exception thrown when exiting from method1
).将
method2
注释为@Transactional(readOnly = true)
仍将事务标记为回滚(从method1
退出时抛出异常)。
Annotating both method1
and method2
as @Transactional(readOnly = true)
still marks transaction for rollback (exception thrown when exiting from method1
).将
method1
和method2
注释为@Transactional(readOnly = true)
仍将事务标记为回滚(从method1
退出时抛出异常)。
Annotating method2
with @Transactional(noRollbackFor = SomeException)
prevents marking transaction for rollback ( no exception thrown when exiting from method1
).使用
@Transactional(noRollbackFor = SomeException)
注释method2
可防止将事务标记为回滚(从method1
退出时不会抛出异常)。
Suppose method2
belongs to Service1
.假设
method2
属于Service1
。 Invoking it from method1
does not go through Spring's proxy, ie Spring is unaware of SomeException
thrown out of method2
.从
method1
调用它不会通过 Spring 的代理,即 Spring 不知道从method2
抛出SomeException
。 Transaction is not marked for rollback in this case.在这种情况下,事务没有标记为回滚。
Suppose method2
is not annotated with @Transactional
.假设
method2
没有用@Transactional
注释。 Invoking it from method1
does go through Spring's proxy, but Spring pays no attention to exceptions thrown.从
method1
调用它确实要经过 Spring 的代理,但是 Spring 并不关心抛出的异常。 Transaction is not marked for rollback in this case.在这种情况下,事务没有标记为回滚。
Annotating method2
with @Transactional(propagation = Propagation.REQUIRES_NEW)
makes method2
start new transaction.用
@Transactional(propagation = Propagation.REQUIRES_NEW)
注释method2
使method2
开始新的事务。 That second transaction is marked for rollback upon exit from method2
but original transaction is unaffected in this case ( no exception thrown when exiting from method1
).第二个事务在从
method2
退出时被标记为回滚,但在这种情况下原始事务不受影响(从method1
退出时不会抛出异常)。
In case SomeException
is checked (does not extend RuntimeException), Spring by default does not mark transaction for rollback when intercepting checked exceptions ( no exception thrown when exiting from method1
).如果
SomeException
被检查(不扩展 RuntimeException),Spring 默认情况下在拦截检查异常时不标记事务回滚(从method1
退出时不抛出异常)。
For those who can't (or don't want to) setup a debugger to track down the original exception which was causing the rollback-flag to get set, you can just add a bunch of debug statements throughout your code to find the lines of code which trigger the rollback-only flag:对于那些不能(或不想)设置调试器来跟踪导致回滚标志设置的原始异常的人,您可以在整个代码中添加一堆调试语句来查找行触发仅回滚标志的代码:
logger.debug("Is rollbackOnly: " + TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());
Adding this throughout the code allowed me to narrow down the root cause, by numbering the debug statements and looking to see where the above method goes from returning "false" to "true".在整个代码中添加这个允许我缩小根本原因,通过对调试语句进行编号并查看上述方法从返回“false”到“true”的位置。
Save sub object first and then call final repository save method.首先保存子对象,然后调用最终存储库保存方法。
@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";
}
As explained @Yaroslav Stavnichiy if a service is marked as transactional spring tries to handle transaction itself.正如@Yaroslav Stavnichiy 所解释的,如果服务被标记为事务性 spring 会尝试自行处理事务。 If any exception occurs then a rollback operation performed.
如果发生任何异常,则执行回滚操作。 If in your scenario ServiceUser.method() is not performing any transactional operation you can use @Transactional.TxType annotation.
如果在您的场景中 ServiceUser.method() 没有执行任何事务操作,您可以使用 @Transactional.TxType 注释。 'NEVER' option is used to manage that method outside transactional context.
'NEVER' 选项用于在事务上下文之外管理该方法。
Transactional.TxType reference doc is here . Transactional.TxType 参考文档在这里。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.