代码之家  ›  专栏  ›  技术社区  ›  HenryR

jpa-如果不存在则创建实体?

  •  22
  • HenryR  · 技术社区  · 15 年前

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

    我想写一个方法

    <T> T getOrCreate(Class<T> klass, Object primaryKey)
    

    如果数据库中存在具有pk primarykey的对象,则返回所提供类的对象,否则将创建该类的新对象,并将其持久化并返回。

    接下来我要处理的就是在事务中更新对象的所有字段。

    在JPA中,有没有一种惯用的方法可以做到这一点,或者有没有更好的方法来解决我的问题?

    3 回复  |  直到 9 年前
        1
  •  -3
  •   Joshua Taylor    10 年前
    1. 创建一个EntityManager实例(我们称之为“em”),除非您已经有一个活动实例
    2. 创建一个新事务(我们称之为“tx”)。
    3. 调用em.find(对象pk)
    4. 调用tx.Read()
      • 如果find()返回了非空的实体引用,则需要进行更新。将更改应用于返回的实体,然后调用em.merge(对象实体)。
      • 如果find()返回了一个空引用,那么该pk在数据库中不存在。创建一个新实体,然后调用em.persist(对象newEntity)。
    5. 调用EM. FHASH()
    6. 调用tx.commit()。
    7. 根据方法签名返回实体引用。
        2
  •  19
  •   Pascal Thivent    15 年前

    我想写一个方法 <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,那么同步不会有帮助。如果不获取一个表锁(这很可怕),我真的不知道你如何解决这个问题。

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

    您可能应该添加一些关于上述约束(多线程)的详细信息。分布式环境?).

        3
  •  4
  •   Pace    9 年前

    使用纯JPA可以用嵌套的实体管理器(实际上我们只需要嵌套事务,但我认为纯JPA不可能)在多线程解决方案中优化地解决这个问题。本质上,我们需要创建一个封装find或create操作的微事务。这个性能不会太好,不适合大批量创建,但对于大多数情况来说应该足够。

    先决条件:

    • 实体必须具有唯一的约束冲突,如果创建两个实例,该冲突将失败。
    • 您有某种查找工具来查找实体(可以通过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();
    }