JPA - create-if-not-exists entity?

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?

23
задан HenryR 25 August 2010 в 00:48
поделиться

2 ответа

  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. Возвращает ссылку на вашу сущность согласно вашей сигнатуре метода.
-3
ответ дан 29 November 2019 в 02:52
поделиться

Я бы хотел написать такой метод, как T getOrCreate(Class 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, синхронизация не поможет. И без блокировки таблицы (что довольно ужасно) я действительно не понимаю, как вы могли бы решить эту проблему.

В таком случае я задаюсь вопросом, не лучше ли было бы сначала систематически вставлять и обрабатывать возможное исключение для выполнения последующего выбора (в новой транзакции).

Вероятно, вам следует добавить некоторые подробности относительно упомянутых ограничений (многопоточность? распределенная среда?).

20
ответ дан 29 November 2019 в 02:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: