休眠会话#合并是否应该在接收具有 ID 的实体时进行插入



这似乎经常出现,但我用谷歌搜索无济于事。

假设您有一个休眠实体User。数据库中有一个 ID 为 1 的User

您有两个线程正在运行,A 和 B。它们执行以下操作:

  • A 获取用户 1,closeSession
  • B 获取用户 1 并delete
  • A 更改用户 1 上的字段
  • A 获取新Sessionmerge用户 1

我所有的测试都表明merge试图在数据库中找到用户 1(显然它不能),因此它插入了一个 id 为 2 的新用户。

另一方面,我的期望是Hibernate会看到被合并的用户不是新的(因为它有一个ID)。它将尝试在数据库中查找用户,这将失败,因此它不会尝试插入或更新。理想情况下,它会引发某种并发异常。

请注意,我使用的是乐观锁定通过@Version,这无济于事。

所以,问题:

  1. 我观察到的休眠行为是预期的行为吗?
  2. 如果是这样,在 JPAEntityManager而不是休眠Session上调用merge时是否具有相同的行为?
  3. 如果2.的答案是肯定的,为什么没有人抱怨呢?

请参阅下面的休眠文档中的文本。

将给定对象的状态复制到具有相同标识符的持久对象上。如果当前没有与会话关联的持久实例,则将加载该实例。返回持久实例。如果给定实例未保存,请保存 的副本并将其作为新的持久实例返回。

它清楚地指出,复制数据库中对象的状态(数据)。 如果对象不存在,则保存该数据的副本。当我们说保存副本休眠时,请始终使用新标识符创建记录。

休眠合并函数的工作方式如下。

  1. 检查实体的状态(附加到或分离到会话)并发现它已分离。
  2. 然后,它尝试加载带有标识符但在数据库中找不到的实体。
  3. 由于找不到实体,因此它将该实体视为暂时性实体。
  4. 暂时性实体始终使用新标识符创建新的数据库记录。

锁定始终应用于附着的实体。如果实体是分离的,则休眠将始终加载它并更新版本值。

锁定用于控制并发问题。这不是并发问题。

我一直在研究JSR-220,Session#merge声称从中获取其语义。可悲的是,JSR是模棱两可的,我发现。

它确实说:

乐观锁定是一种用于确保更新的技术 对数据库数据进行与实体状态相对应的数据 仅当没有干预事务更新该数据时,自 已读取实体状态。

如果您采用"更新"来包括数据库数据的一般突变,包括删除,而不仅仅是 SQLUPDATE,我认为您可以提出一个论点,即观察到的行为不符合乐观锁定。

鉴于对我的问题的评论以及随后发现的此错误,许多人都同意。

从纯粹的实际角度来看,无论是否合规,都可能导致相当多的错误,因为它与许多开发人员的期望背道而驰。似乎没有一个简单的解决方法。事实上,Spring Data JPA似乎完全忽略了这个问题,盲目使用EM#merge。也许其他JPA提供商以不同的方式处理这个问题,但是使用Hibernate可能会导致问题。

我实际上目前正在使用Session#update来解决这个问题。它真的很丑陋,当你尝试update一个分离的实体并且已经有一个托管副本时,需要代码来处理这种情况。但是,它也不会导致虚假插入。

我观察到的休眠行为 1.Is 预期行为?

行为是正确的。您只是尝试执行不受并发数据修改保护的操作:)如果必须将操作拆分为两个会话。只需再次找到要更新的对象并检查它是否仍然存在,如果没有,则引发异常。如果有,则使用 em 锁定它。(类、主键、锁定模式类型);或使用@Version或@Entity(optimisticLock=OptimisticLockType.ALL/DIRTY/VERSION)来保护对象,直到事务结束。

2.如果是这样,在 JPA 实体管理器而不是休眠会话上调用合并时的行为是否相同?

可能:是

的3.如果2.的答案是肯定的,为什么没有人抱怨它?

因为如果您使用悲观或乐观锁定来保护您的操作,问题将消失:)

您尝试解决的问题称为:不可重复读取

最新更新