使用JPA一次创建新实体或更新现有实体

时间:2010-02-25 18:21:23

标签: jpa insert-update

有一个JPA实体,它具有时间戳字段,并由复杂的标识符字段区分。我需要的是更新已经存储的实体中的时间戳,否则使用当前时间戳创建和存储新实体。

事实证明,任务并不像第一眼看上去那么简单。问题是在并发环境中我遇到了令人讨厌的“唯一索引或主键冲突”异常。这是我的代码:

// Load existing entity, if any.
Entity e = entityManager.find(Entity.class, id);
if (e == null) {
  // Could not find entity with the specified id in the database, so create new one.
  e = entityManager.merge(new Entity(id));
}
// Set current time...
e.setTimestamp(new Date());
// ...and finally save entity.
entityManager.flush();

请注意,在此示例中,实体标识符不会在插入时生成,因此事先已知。

当两个或多个线程并行运行此代码块时,它们可能同时从null方法调用获得entityManager.find(Entity.class, id),因此它们将尝试同时保存两个或更多实体,使用相同的标识符导致错误。

我认为解决问题的方法很少。

  1. 当然,我可以将此代码块与全局锁同步以防止并发访问数据库,但这是否是最有效的方式?
  2. 某些数据库支持非常方便的MERGE语句,用于更新现有语句或创建新行(如果不存在)。但我怀疑OpenJPA(我选择的JPA实现)是否支持它。
  3. 事件如果JPA不支持SQL MERGE,我总是可以回到普通的旧JDBC并对数据库做任何我想做的事情。但我不想留下舒适的API,而是要使用毛茸茸的JDBC + SQL组合。
  4. 只使用标准的JPA API修复它,但我还不知道。
  5. 请帮忙。

1 个答案:

答案 0 :(得分:1)

您指的是JPA事务的事务隔离。即当交易访问其他交易资源时,交易的行为是什么。

根据this article

  

READ_COMMITTED是使用[..] EJB3 JPA的预期默认事务隔离级别

这意味着 - 是的,您将遇到上述代码的问题。

但JPA不支持自定义隔离级别。

This thread更广泛地讨论了这个主题。根据您使用的是Spring还是EJB,我认为您可以使用正确的事务策略。