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

use*_*601 39 java spring hibernate jpa

我正在使用Spring和Hibernate在我正在处理的一个应用程序中,我遇到了处理事务的问题.

我有一个加载一些实体从数据库中的服务类,修改了一些它们的值,然后(当一切都有效)提交这些更改到数据库.如果新值无效(我只能在设置后检查),我不想保留更改.为了防止Spring/Hibernate保存更改,我在方法中抛出异常.但是会导致以下错误:

Could not commit JPA transaction: Transaction marked as rollbackOnly
Run Code Online (Sandbox Code Playgroud)

这是服务:

@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();
    }

  }
}
Run Code Online (Sandbox Code Playgroud)

这就是我调用它的方式:

class ServiceUser {
  @Autowired
  private MyService myService;

  public void method() {
    try {
      myService.doSth();
    } catch (MyCustomException e) {
      // ...
    }        
  }
}
Run Code Online (Sandbox Code Playgroud)

我期望发生的事情:没有对数据库的更改,也没有对用户可见的异常.

会发生什么:没有更改数据库但应用程序崩溃:

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
Run Code Online (Sandbox Code Playgroud)

它正确地将事务设置为rollbackOnly,但为什么回滚会因异常而崩溃?

JB *_*zet 58

我的猜测是,这ServiceUser.method()本身就是交易性的.它不应该.这就是原因所在.

以下是调用ServiceUser.method()方法时发生的情况:

  1. 事务拦截器拦截方法调用,并启动事务,因为没有事务已经处于活动状态
  2. 该方法被调用
  3. 该方法调用MyService.doSth()
  4. 事务拦截器拦截方法调用,发现事务已经处于活动状态,并且不执行任何操作
  5. doSth()被执行并抛出异常
  6. 事务拦截器拦截异常,将事务标记为rollbackOnly,并传播异常
  7. ServiceUser.method()捕获异常并返回
  8. 事务拦截器,因为它已启动事务,尝试提交它.但是Hibernate拒绝这样做是因为事务被标记为rollbackOnly,所以Hibernate会抛出异常.事务拦截器通过抛出包装hibernate异常的异常来向调用者发送信号.

现在,如果ServiceUser.method()不是事务性的,那么会发生什么:

  1. 该方法被调用
  2. 该方法调用MyService.doSth()
  3. 事务拦截器拦截方法调用,发现没有事务已经处于活动状态,从而启动事务
  4. doSth()被执行并抛出异常
  5. 事务拦截器拦截异常.由于它已经启动了事务,并且由于抛出了异常,因此它会回滚事务并传播异常
  6. ServiceUser.method()捕获异常并返回

  • 你可以使用REQUIRES_NEW作为doSth()方法.然后,doSth方法将在其自己的事务中执行,该事务可以在不影响method()事务的情况下回滚. (3认同)

Yar*_*hiy 20

无法提交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();
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

SomeException 取消选中(扩展RuntimeException),除非另有说明.

场景:

  1. 标记为由异常抛出的回滚标记的事务method2.这是JB Nizet解释的默认情况.

  2. 注释method2作为@Transactional(readOnly = true)仍然标志着回滚事务(从退出时抛出的异常method1).

  3. 注释都method1method2作为@Transactional(readOnly = true)仍然标志着回滚事务(从退出时抛出的异常method1).

  4. 注释method2用于@Transactional(noRollbackFor = SomeException)防止标记事务以进行回滚(退出时不会抛出异常method1).

  5. 假设method2属于Service1.调用它method1不会通过Spring的代理,即Spring不知道SomeException被抛出method2.在这种情况下,事务未标记为回滚.

  6. 假设method2没有注释@Transactional.调用它method1确实通过Spring的代理,但Spring不关注抛出的异常.在这种情况下,事务未标记为回滚.

  7. 注解method2@Transactional(propagation = Propagation.REQUIRES_NEW)品牌method2启动新的事务.第二个事务在退出时标记为回滚,method2但在这种情况下原始事务不受影响(退出时不会抛出异常method1).

  8. 如果SomeException选中(不继承RuntimeException),Spring缺省不适用于回滚事务标记时拦截检查的异常(也不例外,从退出时抛出method1).

查看此要点中测试的所有方案.