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

发布于 2024-08-23 04:00:04 字数 1027 浏览 7 评论 0原文

A 有一个 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();

请注意,在此示例中,实体标识符不是在插入时生成的,而是预先知道的。

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

我认为解决这个问题的办法很少。

  1. 当然,我可以将此代码块与全局锁同步以防止并发访问数据库,但这会是最有效的方法吗?
  2. 某些数据库支持非常方便的 MERGE 语句,该语句可以更新现有行或创建新行(如果不存在)。但我怀疑 OpenJPA(我选择的 JPA 实现)是否支持它。
  3. 如果 JPA 不支持 SQL MERGE,我总是可以退回到普通的旧式 JDBC,并对数据库执行任何我想要的操作。但我不想留下舒适的 API 和混乱的 JDBC+SQL 组合。
  4. 有一个魔术技巧可以仅使用标准 JPA API 来修复它,但我还不知道。

请帮忙。

A have a JPA entity that has timestamp field and is distinguished by a complex identifier field. What I need is to update timestamp in an entity that has already been stored, otherwise create and store new entity with the current timestamp.

As it turns out the task is not as simple as it seems from the first sight. The problem is that in concurrent environment I get nasty "Unique index or primary key violation" exception. Here's my code:

// 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();

Please note that in this example entity identifier is not generated on insert, it is known in advance.

When two or more of threads run this block of code in parallel, they may simultaneously get null from entityManager.find(Entity.class, id) method call, so they will attempt to save two or more entities at the same time, with the same identifier resulting in error.

I think that there are few solutions to the problem.

  1. Sure I could synchronize this code block with a global lock to prevent concurrent access to the database, but would it be the most efficient way?
  2. Some databases support very handy MERGE statement that updates existing or creates new row if none exists. But I doubt that OpenJPA (JPA implementation of my choice) supports it.
  3. Event if JPA does not support SQL MERGE, I can always fall back to plain old JDBC and do whatever I want with the database. But I don't want to leave comfortable API and mess with hairy JDBC+SQL combination.
  4. There is a magic trick to fix it using standard JPA API only, but I don't know it yet.

Please help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一向肩并 2024-08-30 04:00:04

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

根据这篇文章

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

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

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

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

You are referring to the transaction isolation of JPA transactions. I.e. what is the behaviour of transactions when they access other transactions' resources.

According to this article:

READ_COMMITTED is the expected default Transaction Isolation level for using [..] EJB3 JPA

This means that - yes, you will have problems with the above code.

But JPA doesn't support custom isolation levels.

This thread discusses the topic more extensively. Depending on whether you use Spring or EJB, I think you can make use of the proper transaction strategy.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文