Java JPA - 创建如果不存在实体?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3562105/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
JPA - create-if-not-exists entity?
提问by HenryR
I have several mapped objects in my JPA / Hibernate application. On the network I receive packets that represent updates to these objects, or may in fact represent new objects entirely.
我的 JPA/Hibernate 应用程序中有几个映射对象。在网络上,我收到代表这些对象更新的数据包,或者实际上可能完全代表新对象。
I'd like to write a method like
我想写一个方法,如
<T> T getOrCreate(Class<T> klass, Object primaryKey)
that returns an object of the provided class if one exists in the database with pk primaryKey, and otherwise creates a new object of that class, persists it and returns it.
如果数据库中存在具有 pk primaryKey 的对象,则返回所提供类的对象,否则创建该类的新对象,将其持久化并返回。
The very next thing I'll do with the object will be to update all its fields, within a transaction.
接下来我要对对象做的事情就是在一个事务中更新它的所有字段。
Is there an idiomatic way to do this in JPA, or is there a better way to solve my problem?
在 JPA 中是否有一种惯用的方法来执行此操作,或者是否有更好的方法来解决我的问题?
采纳答案by Jim Tough
- Create an EntityManager instance (let's call it "em"), unless you already have an active one
- Create a new transaction (let's call it "tx")
- Call em.find(Object pk)
- Call tx.begin()
- If find() returned a non-null entity reference then you need to do an update. Apply your changes to the returned entity and then call em.merge(Object entity).
- if find() returned a null reference, then that PK does not exist in the database. Create a new entity and then call em.persist(Object newEntity).
- Call em.flush()
- Call tx.commit()
- Return your entity reference, per your method signature.
- 创建一个 EntityManager 实例(我们称之为“em”),除非你已经有一个活动的
- 创建一个新交易(我们称之为“tx”)
- 调用 em.find(Object pk)
- 调用 tx.begin()
- 如果 find() 返回非空实体引用,则您需要进行更新。将您的更改应用于返回的实体,然后调用 em.merge(Object entity)。
- 如果 find() 返回空引用,则该 PK 不存在于数据库中。创建一个新实体,然后调用 em.persist(Object newEntity)。
- 调用 em.flush()
- 调用 tx.commit()
- 根据您的方法签名返回您的实体引用。
回答by Pascal Thivent
I'd like to write a method like
<T> T getOrCreate(Class<T> klass, Object primaryKey)
我想写一个方法,如
<T> T getOrCreate(Class<T> klass, Object primaryKey)
This won't be easy.
这不会很容易。
A naive approach would be to do something like this (assuming the method is running inside a transaction):
一种天真的方法是做这样的事情(假设该方法在事务中运行):
public <T> T findOrCreate(Class<T> entityClass, Object primaryKey) {
T entity = em.find(entityClass, primaryKey);
if ( entity != null ) {
return entity;
} else {
try {
entity = entityClass.newInstance();
/* use more reflection to set the pk (probably need a base entity) */
return entity;
} catch ( Exception e ) {
throw new RuntimeException(e);
}
}
}
But in a concurrent environment, this code could fail due to some race condition:
但是在并发环境中,由于某些竞争条件,此代码可能会失败:
T1: BEGIN TX; T2: BEGIN TX; T1: SELECT w/ id = 123; //returns null T2: SELECT w/ id = 123; //returns null T1: INSERT w/ id = 123; T1: COMMIT; //row inserted T2: INSERT w/ name = 123; T2: COMMIT; //constraint violation
And if you are running multiple JVMs, synchronization won't help. And without acquiring a table lock (which is pretty horrible), I don't really see how you could solve this.
如果您正在运行多个 JVM,同步将无济于事。如果没有获得表锁(这非常可怕),我真的不知道如何解决这个问题。
In such case, I wonder if it wouldn't be better to systematically insert first and handle a possible exception to perform a subsequent select (in a new transaction).
在这种情况下,我想知道首先系统地插入并处理可能的异常以执行后续选择(在新事务中)是否更好。
You should probably add some details regarding the mentioned constraints (multi-threading? distributed environment?).
您可能应该添加一些关于上述约束的细节(多线程?分布式环境?)。
回答by Pace
Using pure JPA one can solve this optimistically in a multi-threaded solution with nested entity managers (really we just need nested transactions but I don't think that is possible with pure JPA). Essentially one needs to create a micro-transaction that encapsulates the find-or-create operation. This performance won't be fantastic and isn't suitable for large batched creates but should be sufficient for most cases.
使用纯 JPA 可以在带有嵌套实体管理器的多线程解决方案中乐观地解决这个问题(实际上我们只需要嵌套事务,但我认为纯 JPA 不可能实现)。本质上,需要创建一个封装了查找或创建操作的微事务。这种性能不会太棒,也不适合大批量创建,但对于大多数情况应该足够了。
Prerequisites:
先决条件:
- The entity must have a unique constraint violation that will fail if two instances are created
- You have some kind of finder to find the entity (can find by primary key with EntityManager.find or by some query) we will refer to this as
finder
- You have some kind of factory method to create a new entity should the one you are looking for fail to exist, we will refer to this as
factory
. - I'm assuming that the given findOrCreate method would exist on some repository object and it is called in the context of an existing entity manager and an existing transaction.
- If the transaction isolation level is serializable or snapshot this won't work. If the transaction is repeatable read then you must not have attempted to read the entity in the current transaction.
- I'd recommend breaking the logic below into multiple methods for maintainability.
- 实体必须有一个唯一的约束违规,如果创建两个实例,该违规将失败
- 您有某种查找器来查找实体(可以使用 EntityManager.find 通过主键查找或通过某些查询查找)我们将其称为
finder
- 如果您要查找的实体不存在,您可以使用某种工厂方法来创建新实体,我们将其称为
factory
. - 我假设给定的 findOrCreate 方法存在于某个存储库对象上,并且在现有实体管理器和现有事务的上下文中调用它。
- 如果事务隔离级别是可序列化的或快照,这将不起作用。如果事务是可重复读取的,那么您一定没有尝试读取当前事务中的实体。
- 我建议将下面的逻辑分解为多种方法以实现可维护性。
Code:
代码:
public <T> T findOrCreate(Supplier<T> finder, Supplier<T> factory) {
EntityManager innerEntityManager = entityManagerFactory.createEntityManager();
innerEntityManager.getTransaction().begin();
try {
//Try the naive find-or-create in our inner entity manager
if(finder.get() == null) {
T newInstance = factory.get();
innerEntityManager.persist(newInstance);
}
innerEntityManager.getTransaction().commit();
} catch (PersistenceException ex) {
//This may be a unique constraint violation or it could be some
//other issue. We will attempt to determine which it is by trying
//to find the entity. Either way, our attempt failed and we
//roll back the tx.
innerEntityManager.getTransaction().rollback();
T entity = finder.get();
if(entity == null) {
//Must have been some other issue
throw ex;
} else {
//Either it was a unique constraint violation or we don't
//care because someone else has succeeded
return entity;
}
} catch (Throwable t) {
innerEntityManager.getTransaction().rollback();
throw t;
} finally {
innerEntityManager.close();
}
//If we didn't hit an exception then we successfully created it
//in the inner transaction. We now need to find the entity in
//our outer transaction.
return finder.get();
}