首页 文章

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

提问于
浏览
20

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

我想写一个像这样的方法

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

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

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

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

3 回答

  • -3
    • 创建一个EntityManager实例(让我们称之为"em"),除非你已经有一个活动实例

    • 创建一个新事务(让我们称之为"tx")

    • 调用em.find(Object pk)

    • 调用tx.begin()

    • 如果find()返回非空实体引用,则需要进行更新 . 将更改应用于返回的实体,然后调用em.merge(对象实体) .

    • 如果find()返回空引用,那么数据库中不存在该PK . 创建一个新实体,然后调用em.persist(Object newEntity) .

    • 调用em.flush()

    • 致电tx.commit()

    • 根据您的方法签名返回您的实体引用 .

  • 4

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

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

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

  • 19

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

相关问题