JPA-create-if-not-exists实体?[英] JPA - create-if-not-exists entity?

本文是小编为大家收集整理的关于JPA-create-if-not-exists实体?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

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

我想编写一个

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

,如果一个使用PK Primary Kepry在数据库中存在,则返回提供类的对象,否则会创建该类的新对象,将其坚持并返回.

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

是否有一种惯用方法可以在JPA中执行此操作,还是有更好的方法来解决我的问题?

推荐答案

我想编写<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,则同步无济于事.而且,如果没有获得桌子锁(这很可怕),我真的看不到如何解决这个问题.

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

.

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

其他推荐答案

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

其他推荐答案

我必须指出 @gus and的答案.在并发情况下,这可能导致明显的问题.如果有2个线程读取计数,它们都将获得0,然后进行插入.因此创建了重复的行.

我的建议是写下您的本地查询,如下所示:

insert into af_label (content,previous_level_id,interval_begin,interval_end) 
    select "test",32,9,13
    from dual 
    where not exists (select * from af_label where previous_level_id=32 and interval_begin=9 and interval_end=13)

就像程序中的乐观锁一样.但是我们使数据库引擎决定并通过您的自定义属性找到重复项.

本文地址:https://www.itbaoku.cn/post/597600.html

问题描述

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.

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.

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?

推荐答案

I'd like to write a method like <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.

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?).

其他推荐答案

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.

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.

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

其他推荐答案

I must point out there's some flaw in @gus an's answer. It could lead to an apparent problem in a concurrent situation. If there are 2 threads reading the count, they would both get 0 and then do the insertion. So duplicate rows created.

My suggestion here is to write your native query like the one below:

insert into af_label (content,previous_level_id,interval_begin,interval_end) 
    select "test",32,9,13
    from dual 
    where not exists (select * from af_label where previous_level_id=32 and interval_begin=9 and interval_end=13)

It's just like an optimistic lock in the program. But we make the db engine to decide and find the duplicates by your customized attributes.