合并不存在的ID

时间:2018-06-23 03:20:15

标签: hibernate jpa

众所周知,引入EntityManager.merge()可以将对分离的实体所做的更改合并到一个附加的实体中。这暗示了一种模式“获取实体,将其浅表副本返回给某些客户端,让客户端进行一些更改并向我们发送更新后的实例,然后将更新后的实例合并回到持久性上下文中”,

但是如果实例在客户端正在使用其分离副本播放时被第三方删除了怎么办?当我们尝试将其合并回时,可能会期望其行为类似于SQL更新:抛出某种“实体不存在”的异常。一个人会很失望:在这种情况下,JPA(至少在Hibernate实现中)将简单地忽略提供的ID,并将创建一个新实体,而不是更新现有实体。

我该如何处理?当客户端尝试更新已删除的实体时,我当然不想创建新实体。

我想我可以在合并之前按id找到实体(如果找不到则抛出),并依靠“可重复读取”来防止实体删除(如果有)在当前事务中可见,从而确保merge()更新现有实体。但是,这有点丑陋,尤其是考虑到merge()如何级联到嵌套实体,而我也必须找到find()。我还尝试介绍@version希望它会有所帮助,如果我提供的是非null基本版本,JPA会发现创建新实体不合适,但这似乎也被忽略了。

P.S。在我看来,我可以允许JPA创建一个新实体,然后对照客户端提供的ID来检查其ID。例如,如果使用一个序列来生成ID,则新ID将与旧ID不同,在这种情况下,我可以抛出异常并回滚事务。但这仍然是额外的检查,因此此解决方案并不比使用find()更好。

2 个答案:

答案 0 :(得分:2)

您可以在合并后检查密钥是否相同。
如果相同,则表示合并的对象相同;如果不相同,则表示分离的实体已被删除。

    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正在这样做:

  • SELECT ... FROM Entity WHERE id = id_of_detached_entity
  • 如果上面的查询找到一行,则Hibernate将相同的ID赋给合并的实体对象
  • 如果上面的查询未返回任何内容,则Hibernate从序列中获取下一个Id值,并将此值分配给合并的实体对象,因此合并的对象具有与旧的分离对象不同的ID。

答案 1 :(得分:0)

几天前我遇到了这个问题,对此我的想法与您的想法相同。但是,仔细阅读了通过OptimisticLockException得到的简单消息后:“行已被另一个事务更新或删除”,我意识到从锁定的角度来看,我们可能错过了一些重要的东西:

  • 每种锁定类型都需要在其下进行活动交易,通过交易获得的任何锁定都需要在交易关闭之前被删除,因此即使我们使用乐观锁定版本,在外部交易中也没有任何意义,

  • 要获取OptimisticLockException,我们需要有一个已附加到事务上下文的实体,在合并分离的实体时,我们还没有这样的实体,

总结,禁止在外部事务中使用版本控制,但是将其注入新事务中会产生意外结果。

我的JPA解决方案如下:

@Transactional
public Account update(Account account) {
    if (accountJpaRepository.findById(account.getId()).isPresent()) {
        return accountJpaRepository.save(account);
    } else {
        throw new UpdatedEntityNotFoundException("Updated Account not found");
    }
}
  • @Transactional-打开交易上下文
  • accountJpaRepository.findById(account.getId())。isPresent()-如果数据库中存在提供的实体ID,则按ID进行检查;如果存在,我们将其副本放入事务上下文中
  • accountJpaRepository.save-通过将分离的实体与我们已经在上下文中拥有的实体(而不是数据库中的实体)合并来保存分离的实体

在此解决方案中,我们仍然使用乐观锁定,因此,如果在调用find和save之间对数据库中的实体发生某些事情(更新,删除),则save将引发OptimisticLockException。

好,那级联呢?我发现了这样的东西:

@OptimisticLock(excluded = false)
@OneToMany(mappedBy = "transaction", cascade = CascadeType.ALL, orphanRemoval = true)
private List<TransactionCategory> transactionCategories;

@OptimisticLock(excluded = false)-它使对列表或其项目所做的任何更改都会增加嵌套实体的版本,即使没有直接更改。如您在示例中看到的那样,存在CascadeType.ALL,因此关系由嵌套实体完全管理,但是我没有分析过更多的层叠类型,对于每种层叠类型,解决方案都需要重新考虑。