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

问题描述 投票:3回答:2

我有这样一个实体:

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),以及更新Child实体的常规操作。

问题是我们使用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

或者,如果这不能解决问题。

java spring spring-boot jpa optimistic-locking
2个回答
1
投票

当然,建议为Child本身添加存储库,但我怀疑它会解决您的问题。

您仍然需要使用子级更新父级,因此如果我正确理解您的情况,您仍然会有两个parentRepository.save(parent);操作。因此,无论如何你最终会得到一个OptimisticLockException

我只是应用处理这种异常的一般程序,即:

  • 抓住OptimisticLockException
  • 合并保存异常的实体
  • 再次重试持久性/更新

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,你会看到像children一样的10张Y记录什么也没做。
  5. 但!当你想在事务children中删除/更新X记录时,Hibernate会这样做,但删除/更新会返回受影响记录的数量。

所以这是一个棘手的事情 - 受影响的记录数将等于零,因为事务Y已经删除了所有记录(这就是所谓的幻像读取)。但是Hibernate认为他应该删除10条记录。在收到0个删除的记录后,Hibernate提升了ObjectOptimisticLockingFailureException,因为Hibernate认为这种情况是异常的。

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

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

Understanding MySQL Isolation Levels: Repeatable-Read

© www.soinside.com 2019 - 2024. All rights reserved.