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

fdx2calv  于 2023-05-05  发布在  Java
关注(0)|答案(6)|浏览(185)

我在一个应用程序中使用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,但为什么回滚崩溃并出现异常?

2eafrhcq

2eafrhcq1#

我的猜测是ServiceUser.method()本身是事务性的。这不应该是。原因如下。
下面是调用ServiceUser.method()方法时发生的情况:
1.事务性拦截器拦截方法调用,并启动事务,因为没有活动的事务
1.方法被调用
1.该方法调用MyService.doSth()
1.事务拦截器拦截方法调用,发现事务已经处于活动状态,不做任何事情
1.执行doSth()并抛出异常
1.事务拦截器拦截异常,将事务标记为rollbackOnly,并传播异常

  1. method()捕获异常并返回
    1.由于事务拦截器已经启动了事务,所以它尝试提交该事务。但是Hibernate拒绝这样做,因为事务被标记为rollbackOnly,所以Hibernate抛出一个异常。事务拦截器通过抛出一个 Package hibernate异常的异常来向调用者发出信号。
    现在,如果ServiceUser.method()不是事务性的,会发生以下情况:
    1.方法被调用
    1.该方法调用MyService.doSth()
    1.事务拦截器拦截方法调用,发现没有活动的事务,因此启动事务
    1.执行doSth()并抛出异常
    1.事务拦截器拦截该异常。由于它已经启动了事务,并且抛出了异常,因此它将回滚事务并传播异常
  2. method()捕获异常并返回
0s7z1bwu

0s7z1bwu2#

无法提交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解释的默认情况。
1.将method2注解为@Transactional(readOnly = true)仍然会将事务标记为回滚(从method1退出时抛出异常)。
1.将method1method2都注解为@Transactional(readOnly = true)仍然会将事务标记为回滚(从method1退出时抛出异常)。
1.使用@Transactional(noRollbackFor = SomeException)注解method2,可以防止将事务标记为回滚(从method1退出时无异常抛出)。
1.假设method2属于Service1。从method1调用它不会通过Spring的代理,即Spring不知道SomeExceptionmethod2中抛出。本次交易未标记回滚
1.假设method2没有用@Transactional注解。从method1调用它确实要经过Spring的代理,但是Spring不会注意抛出的异常。本次交易未标记回滚
1.用@Transactional(propagation = Propagation.REQUIRES_NEW)注解method2使method2启动新事务。第二个事务在退出method2时被标记为回滚,但在这种情况下,原始事务不受影响(从method1退出时无异常抛出)。
1.如果SomeExceptionchecked(不扩展RuntimeException),Spring默认在拦截checked异常时不标记transaction进行回滚(从method1退出时no exception抛出)。
查看this gist中测试的所有场景。

9ceoxa92

9ceoxa923#

对于那些不能(或不想)设置调试器来跟踪导致回滚标志设置的原始异常的人,您可以在整个代码中添加一堆调试语句来查找触发仅回滚标志的代码行:

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

在整个代码中添加这个允许我缩小根本原因,通过编号调试语句并查看上面的方法从返回“false”到“true”的位置。

oxcyiej7

oxcyiej74#

正如@Yaroslav Stavnichiy所解释的,如果一个服务被标记为transactional,spring会尝试自己处理transaction。如果发生任何异常,则执行回滚操作。如果在您的场景中ServiceUser.method()不执行任何事务操作,您可以使用@Transactional.TxType注解。“NEVER”选项用于在事务上下文之外管理该方法。
Transactional.TxType参考文档为here

cdmah0mi

cdmah0mi5#

首先保存子对象,然后调用最终存储库保存方法。

@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";
    }
e4yzc0pl

e4yzc0pl6#

对我来说,这是由于违反了约束,当我试图使用保存用null值更新not null字段时。

相关问题