更新子实体但保存父实体会导致ObjectOptimisticLockingFailureException

时间:2018-12-14 10:21:49

标签: java spring spring-boot jpa optimistic-locking

我有一个这样的实体:

class Parent {
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<ChildUpdatedByBatch> childrenUpdatedByBatch;
    @OneToOne(mappedBy = "parent", cascade = CascadeType.ALL)
    private Child child;
....
}

我有一个批处理作业,该作业最终会保存/更新一个子实体( ChildUpdatedByBatch ),还有一个常规操作来更新该子实体。

问题是,我们使用 parentRepository 更新两个实体,因此批处理作业具有以下内容:

// updating parent entity by adding/ or updating a ChildUpdatedByBatch
parentRepository.save(parent);

常规操作还使用:

// updating parent entity by adding/ or updating the Child entity
parentRepository.save(parent);

但是第二个(常规操作)它抛出ObjectOptimisticLockingFailureException,因为

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [<package>.ChildEntityUpdatedByBatch#911].

我想知道,是否为每个子实体创建存储库都能解决问题。我的意思是,像这样:

child.save(child) // with child having a reference to out-of-date parent

或者,如果那样也不能解决问题。

2 个答案:

答案 0 :(得分:1)

Child本身添加存储库是明智的,但是我怀疑这会解决您的问题。

您仍然必须与孩子一起更新父母,因此,如果我正确理解您的情况,您将仍然有两个parentRepository.save(parent);操作。因此,无论如何您最终都会得到OptimisticLockException

我将简单地应用通用程序来处理这种异常,即:

  • 赶上OptimisticLockException
  • 合并发生保存异常的实体
  • 重试持久性/再次更新

答案 1 :(得分:0)

我有一个类似的问题,也许我可以澄清问题的根源。

MySQL JDBC 驱动程序(和 MySQL 数据库)使用REPEATABLE READ作为默认事务隔离级别。它比例如Oracle默认的READ COMMITTED级别受到限制。

所以我认为会发生什么。

  1. 交易X已打开。
  2. Hibernate已为Parent加载了10条children记录。
  3. 另一个事务Y已启动,并从同一children删除了所有Parent记录。

  4. REPEATABLE READ意味着,如果您将在事务Parent中再次加载X,则会看到与{{1}类似的10条children记录}什么也没做。

  5. 但是!当您要删除/更新事务Y中的children条记录时,Hibernate会这样做,但是delete / update返回受影响的记录数。

这是一件棘手的事情-受影响的记录数将等于零,因为事务X已删除了所有记录(这就是所谓的 phantom read )。但是Hibernate认为他应该删除10条记录。收到0条已删除的记录后,由于Hibernate将此情况视为异常,因此Hibernate上升Y

我通过将MySQL JDBC驱动程序属性中的事务隔离级别更改为ObjectOptimisticLockingFailureException来解决此问题。

在MySQL中实现READ COMMITTED隔离级别非常棘手,因此您可以参考本文。对这个话题有很好的解释

Understanding MySQL Isolation Levels: Repeatable-Read