JPA - 如果不存在创建实体?

Hen*_*ryR 23 java database hibernate jpa

我的JPA/Hibernate应用程序中有几个映射对象.在网络上,我接收代表这些对象更新的数据包,或者实际上可能完全代表新对象.

我想写一个像这样的方法

<T> T getOrCreate(Class<T> klass, Object primaryKey)
Run Code Online (Sandbox Code Playgroud)

如果一个人在PK的PrimaryKey数据库存在,否则创建该类的一个新对象,持续,并将其返回,返回所提供的类的对象.

我将对该对象做的下一件事是在事务中更新其所有字段.

在JPA中有没有惯用的方法,或者有更好的方法来解决我的问题?

Pas*_*ent 20

我想写一个像这样的方法 <T> T getOrCreate(Class<T> klass, Object primaryKey)

这并不容易.

一种天真的方法是做这样的事情(假设方法在事务中运行):

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是在并发环境中,由于某些竞争条件,此代码可能会失败:

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

如果您运行多个JVM,同步将无济于事.并且没有获得表锁(这非常可怕),我真的不知道如何解决这个问题.

在这种情况下,我想知道系统地插入第一个并处理可能的异常以执行后续选择(在新事务中)是否更好.

您应该添加一些有关上述约束的细节(多线程?分布式环境?).


Pac*_*ace 6

使用纯JPA可以在具有嵌套实体管理器的多线程解决方案中很好地解决此问题(确实,我们只需要嵌套事务,但我认为纯JPA不可能做到这一点)。本质上,需要创建一种微交易,其中包含查找或创建操作。这种性能不会太出色,也不适合用于大批量创建,但是对于大多数情况来说应该足够了。

先决条件:

  • 实体必须具有唯一性约束违例,如果创建两个实例,则该约束将失败
  • 您可以使用某种查找程序来查找实体(可以通过EntityManager.find的主键或通过某些查询来查找),我们将其称为 finder
  • 如果您要寻找的实体不存在,则您可以使用某种工厂方法来创建新实体,我们将其称为factory
  • 我假设给定的findOrCreate方法将存在于某个存储库对象上,并且在现有实体管理器和现有事务的上下文中调用它。
  • 如果事务隔离级别是可序列化的或快照,则将无法使用。如果事务是可重复读取的,则您一定没有尝试读取当前事务中的实体。
  • 我建议将以下逻辑分为多种方法以实现可维护性。

码:

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();
}
Run Code Online (Sandbox Code Playgroud)

  • 将 `finder` 调用减少 50%: `public static &lt;T&gt; T findOrCreate( EntityManagerFactory emf,Supplier&lt;T&gt; finder,Supplier&lt;T&gt;factory) { EntityManager em=emf.createEntityManager(); T尝试1=finder.get(); if(attempt1 != null) 返回 attempts1; T 创建=factory.get(); 尝试 { em.getTransaction().begin(); em.persist( 创建 ); em.getTransaction().commit(); 返回 finder.get(); } catch(Exception ex) { em.getTransaction().rollback(); T attempts2=finder.get(); if(attempt2!=null ) 返回 attempts2; 扔前;} catch(Throwable t) { em.getTransaction().rollback(); 扔 t;} 最后 { em.close(); } }` (2认同)