简体   繁体   English

无法提交 JPA 事务:事务标记为 rollbackOnly

[英]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()方法时发生的情况:

  1. the transactional interceptor intercepts the method call, and starts a transaction, because no transaction is already active事务拦截器拦截方法调用,并启动一个事务,因为没有事务已经处于活动状态
  2. the method is called该方法被称为
  3. the method calls MyService.doSth()该方法调用 MyService.doSth()
  4. the transactional interceptor intercepts the method call, sees that a transaction is already active, and doesn't do anything事务拦截器拦截方法调用,看到一个事务已经处于活动状态,不做任何事情
  5. doSth() is executed and throws an exception doSth() 被执行并抛出异常
  6. the transactional interceptor intercepts the exception, marks the transaction as rollbackOnly, and propagates the exception事务拦截器拦截异常,将事务标记为rollbackOnly,并传播异常
  7. ServiceUser.method() catches the exception and returns ServiceUser.method() 捕获异常并返回
  8. the transactional interceptor, since it has started the transaction, tries to commit it.事务拦截器,因为它已经启动了事务,尝试提交它。 But Hibernate refuses to do it because the transaction is marked as rollbackOnly, so Hibernate throws an exception.但是Hibernate拒绝这样做,因为事务被标记为rollbackOnly,所以Hibernate抛出异常。 The transaction interceptor signals it to the caller by throwing an exception wrapping the hibernate exception.事务拦截器通过抛出一个包含休眠异常的异常来向调用者发出信号。

Now if ServiceUser.method() is not transactional, here's what happens:现在,如果ServiceUser.method()不是事务性的,则会发生以下情况:

  1. the method is called该方法被称为
  2. the method calls MyService.doSth()该方法调用 MyService.doSth()
  3. the transactional interceptor intercepts the method call, sees that no transaction is already active, and thus starts a transaction事务拦截器拦截方法调用,看到没有事务已经处于活动状态,从而启动一个事务
  4. doSth() is executed and throws an exception doSth() 被执行并抛出异常
  5. the transactional interceptor intercepts the exception.事务拦截器拦截异常。 Since it has started the transaction, and since an exception has been thrown, it rollbacks the transaction, and propagates the exception由于它已经启动了事务,并且由于抛出了异常,它回滚事务,并传播异常
  6. ServiceUser.method() catches the exception and returns 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 服务: Service1Service2 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:场景:

  1. Transaction marked for rollback by exception thrown out of method2 .标记为例外回滚抛出交易method2 This is our default case explained by JB Nizet.这是 JB Nizet 解释的默认情况。

  2. Annotating method2 as @Transactional(readOnly = true) still marks transaction for rollback (exception thrown when exiting from method1 ).method2注释为@Transactional(readOnly = true)仍将事务标记为回滚(从method1退出时抛出异常)。

  3. Annotating both method1 and method2 as @Transactional(readOnly = true) still marks transaction for rollback (exception thrown when exiting from method1 ).method1method2注释为@Transactional(readOnly = true)仍将事务标记为回滚(从method1退出时抛出异常)。

  4. Annotating method2 with @Transactional(noRollbackFor = SomeException) prevents marking transaction for rollback ( no exception thrown when exiting from method1 ).使用@Transactional(noRollbackFor = SomeException)注释method2可防止将事务标记为回滚(从method1退出时不会抛出异常)。

  5. 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.在这种情况下,事务没有标记为回滚

  6. 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.在这种情况下,事务没有标记为回滚

  7. 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退出时不会抛出异常)。

  8. 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退出时抛出异常)。

See all scenarios tested in this gist .查看本要点中测试的所有场景。

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.

相关问题 无法提交 JPA 事务; 嵌套异常 - 事务标记为 rollbackOnly - Could not commit JPA transaction; nested exception - Transaction marked as rollbackOnly 无法提交JPA事务-RollbackException:事务标记为rollbackOnly - Can't commit JPA transaction - RollbackException: Transaction marked as rollbackOnly Jpa事务javax.persistence.RollbackException:事务标记为rollbackOnly - Jpa transaction javax.persistence.RollbackException: Transaction marked as rollbackOnly javax.persistence.RollbackException:事务标记为rollbackOnly - javax.persistence.RollbackException: Transaction marked as rollbackOnly RollbackException:事务标记为rollbackOnly,尽管将其设置为norollbackfor - RollbackException: Transaction marked as rollbackOnly although setting it as norollbackfor JPA eclipselink:提交时出错:事务回滚,因为事务被设置为RollbackOnly - JPA eclipselink: Error while commit: Transaction rolled back because transaction was set to RollbackOnly 线程“主”中的异常javax.persistence.RollbackException:事务标记为rollbackOnly - Exception in thread “main” javax.persistence.RollbackException: Transaction marked as rollbackOnly 无法提交 JPA 事务/提交事务时出错/NullPointerException - Could not commit JPA transaction / Error while committing the transaction / NullPointerException 无法提交休眠事务 - Could not commit Hibernate transaction 尝试在 spring 项目中进行注册时无法提交 JPA 事务 - Could not commit JPA transaction while trying to do the registration in a spring project
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM