众所周知,EntityManager.merge() 的引入是为了将对分离实体所做的更改合并到附加实体中。这表明了一种模式“获取一个实体,将其浅拷贝返回给某个客户端,让客户端进行一些更改并向我们发送更新的实例,然后将更新的实例合并回持久性上下文中”,对吧?
但是,如果在该客户端正在使用其独立副本时实例被第三方删除了怎么办?当我们尝试将其合并回去时,人们会期望其行为类似于 SQL 更新:抛出一些“实体不存在”之类的异常。人们可能会感到失望:在这种情况下,JPA(至少在 Hibernate 实现中)将简单地忽略提供的 ID,并创建一个新实体而不是更新现有实体。
我该如何处理这个问题?当客户端只是尝试更新已删除的实体时,我当然不想创建新实体。
我想我可以在合并之前通过 id 找到实体(如果找不到则抛出),并依靠“可重复读取”来防止实体删除(如果有)对当前事务可见,确保 merge() 进行更新现有实体。然而,这有点难看,特别是考虑到 merge() 可以级联到嵌套实体,我也必须 find() 这些实体。我还尝试引入 @version 希望它会有所帮助,并且如果我提供非空基本版本,JPA 会检测到创建新实体是不正确的,但这似乎也被忽略了。
附注我突然想到,我可以允许 JPA 创建一个新实体,然后根据客户端提供的 ID 检查其 ID。比如说,如果使用一个序列来生成 ID,则新 ID 将与旧 ID 不同,在这种情况下,我可以抛出异常并回滚事务。但这仍然是额外的检查,因此这个解决方案并不比使用 find() 更好。
最佳答案
合并后可以检查key是否相同。
如果相同,则说明合并对象相同,如果不同,则说明分离实体已被删除。
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
EntityClass mergedEntity = em.merge( detachedEntity );
if( mergedEntity.getId() != detachedEntity.getId() ){
System.out.println("Entity has been deleted !!! Do rollback.");
em.getTransaction().rollback();
}else{
System.out.println("Entity has been merged !!! Do commit ");
em.getTransaction().commit();
}
如果启用 SQL 跟踪,您将看到在执行合并操作时:
mergedEntity = em.merge( detachedEntity );
,Hibernate 在幕后正在执行以下操作:
从实体中选择...,其中 id = id_of_detached_entity
- 如果上述查询找到一行,则 Hibernate 为合并的实体对象分配相同的 Id
- 如果上面的查询没有返回任何内容,Hibernate 将从序列中获取下一个 Id 值,并将该值分配给合并的实体对象,因此合并的对象具有与旧的分离对象不同的 Id。
关于hibernate - 与不存在的 id 合并,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50997657/