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

时间:2010-08-25 00:48:02

标签: java database hibernate jpa

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

我想写一个类似

的方法
<T> T getOrCreate(Class<T> klass, Object primaryKey)

如果数据库中存在pk primaryKey,则返回所提供类的对象,否则会创建该类的新对象,并将其保留并返回。

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

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

4 个答案:

答案 0 :(得分:19)

  

我想写一个像<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);
        }
    }
}

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

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,同步将无济于事。并且没有获得表锁(这非常可怕),我真的不知道如何解决这个问题。

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

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

答案 1 :(得分:4)

使用纯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();
}

答案 2 :(得分:0)

orElse之后使用findByKeyword函数怎么样?如果没有找到记录,您可以返回一个新实例。

        SearchCount searchCount = searchCountRepository.findByKeyword(keyword)
                .orElse(SearchCount.builder()
                        .keyword(keyword)
                        .count(0)
                        .build()) ;

答案 3 :(得分:-3)

  1. 创建一个EntityManager实例(让我们称之为“em”),除非您已经有一个活动
  2. 创建一个新交易(让我们称之为“tx”)
  3. 调用em.find(Object pk)
  4. 致电tx.begin()
    • 如果find()返回非空实体引用,则需要进行更新。将更改应用于返回的实体,然后调用em.merge(对象实体)。
    • 如果find()返回空引用,那么数据库中不存在该PK。创建一个新实体,然后调用em.persist(Object newEntity)。
  5. 致电em.flush()
  6. 致电tx.commit()
  7. 根据您的方法签名返回您的实体引用。