Подтвердить что ты не робот

JPA OneToOne разница между слиянием каскада и сохранением

У меня следующий вопрос. У меня есть 3 объекта, и я использую OneToOne однонаправленной:

entity1

@Entity
public class Entity1 implements Serializable{

   @Id
   @GeneratedValue(strategy= GenerationType.AUTO)
   Long id;
   String name;
   String value;
}

entity2

@Entity
public class Entity2 implements Serializable {
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;

  @OneToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})
  Entity1 entity1;
  public Entity1 getEntity1() {
      return entity1;
  }

  String name;
  String value;
}

Entity3

@Entity
public class Entity3 implements Serializable {
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  Long id;

  @OneToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})
  private Entity1 entity1;

  public Entity1 getEntity1() {
      return entity1;
  }

  public void setEntity1(Entity1 entity1) {
      this.entity1 = entity1;
  }

  String name;
  String value;
}

Небольшой тест:

public void testApp()
   {
    EntityManager em = TestHibernateUtil.getEntityManager();
    em.getTransaction().begin();
    Entity1 entity1 = new Entity1();
    entity1.name = "Name1";
    entity1.value = "Value1";

    Entity2 entity2 = new Entity2();
    entity2.name = "Name2";
    entity2.value = "Value2";
    entity2.setEntity1(entity1);
    **em.merge(entity2);**// if change that to persist - I get one Entity1

    Entity3 entity3 = new Entity3();
    entity3.name = "Name3";
    entity3.value = "Value3";
    entity3.setEntity1(entity1);
    **em.merge(entity3);** // if change that to persist - I get one Entity1
    em.getTransaction().commit();
 }

Итак, рассмотрев приведенный выше тест, если я использую em.merge я получаю 2 сущности Entity1 в контексте постоянства после фиксации транзакции, если я изменяю его на em.persist тогда я получаю одну сущность Entity1 в контексте постоянства. Кто-нибудь может объяснить мне, почему это происходит, или указать на какую-то документацию?

4b9b3361

Ответ 1

Я бы не мечтал оспорить uber ответ DannyMo, но я хотел сделать дополнение:

Persist и merge разработаны как способ сохранить один управляемый экземпляр определенного объекта.

Если вы используете persist, это означает, что объект еще не существует, поэтому сделать его уникальным управляемым экземпляром не повредит.

При использовании слияния вы принимаете во внимание, что управляемый экземпляр объекта может уже существовать. Вы не хотите заменять этот уникальный управляемый экземпляр, потому что другой объект может ссылаться на него, полагая, что это управляемый объект.

Если вы хотите выполнить операции над объектом после слияния, правильное слияние будет выглядеть так:

managedObject = em.merge(object); // or just object = em.merge(object) //You cannot do it with persist since it returns null

Если вы попытались проверить, указывает ли managedObject и object на ту же проверку экземпляра объекта if(managedObject == object), вы получите false (true возможно при использовании слияния на уже управляемом объекте, и операция игнорируется).

Если вы используете слияние на устаревшей версии объекта, которую вы передали как аргумент предыдущему слиянию, jpa не знает, как найти нужный объект, так как он еще не имеет идентификатора. Предполагается, что это новый объект, и будет создан новый управляемый экземпляр.

Я совсем новичок. Пожалуйста, поправьте меня, если я где-то не прав.

Ответ 2

Поведение, которое вы видите, является результатом двух вещей:

  • Семантика операций persist и merge (вызов em.merge(x) не сделает x управляемым объектом, но вызывает em.persist(x))
  • Тот факт, что ваш идентификатор объекта генерируется базой данных

em.merge() rundown:

  • Вызывается
  • em.merge(entity2);
    • Операция слияния каскадируется до entity1
    • entity1 копируется в новый управляемый экземпляр, но не управляется сам
    Вызывается
  • em.merge(entity3);
    • Операция слияния снова каскадируется до entity1
    • Поскольку entity1 по-прежнему неуправляемый и не имеет идентификатора, его нельзя сопоставить с существующим управляемым экземпляром, созданным предыдущим слиянием. В результате создается новый новый экземпляр
  • Сделка совершена
    • На данный момент существует 3 экземпляра entity1. Два управляемых экземпляра, созданных операциями слияния и начальным неуправляемым экземпляром
    • Два управляемых экземпляра сохраняются в базе данных

Обратите внимание, что если ваш объект имел явный идентификатор, то второе слияние не создало бы новый экземпляр, а вместо этого скопировало entity1 в управляемый экземпляр, который уже существовал. Кроме того, если вы попытались объединить уже обработанный экземпляр, тогда вторая операция слияния была бы проигнорирована.

em.persist() rundown:

  • em.persist(entity2); вызывается
    • Операция persist каскадируется до entity1
    • entity1 теперь управляемый объект
  • em.persist(entity3); вызывается
    • Операция persist снова каскадируется на entity1
    • Так как entity1 уже управляется, операция persistent игнорируется
  • Сделка совершена
    • В этот момент существует только 1 экземпляр entity1 и он управляется.
    • entity1 сохраняется в базе данных

Это поведение определено в разделе Спецификация JPA 2.0 3.2.7.1 Слияние отдельного состояния сущности:

Семантика операции слияния, применяемая к объекту X, следующим образом:

  • Если X является новым экземпляром сущности, создается новый экземпляр управляемого объекта X ', и состояние X копируется в новый управляемый объект экземпляр X '.
  • Для всех объектов Y, на которые ссылаются отношения из X, имеющие значение каскадного элемента cascade = MERGE или cascade = ALL, Y объединяется рекурсивно, как Y '. Для всех таких Y, на которые ссылается X, X 'устанавливается равным ссылка Y '. (Заметим, что если X управляется, то X - это тот же объект, что и X".)
  • [...]

и раздел 3.2.2 Сохранение и экземпляр объекта:

Семантика операции persist, применяемая к объекту X, как следующим образом:

  • Если X - новый объект, он становится управляемым. Объект X будет введен в базу данных до или до совершения транзакции или в качестве результат операции промывки.
  • Если X является существующей управляемой сущностью, она игнорируется операцией persist. [...]
  • [...]

См. также: Когда JPA установил @GeneratedValue @Id)