Как избежать дубликатов с каскадами JPA? - PullRequest
9 голосов
/ 27 ноября 2011

У меня есть Родительская сущность с Дочерней сущностью в ManyToOne связи:

@Entity class Parent {
  // ...
  @ManyToOne((cascade = {CascadeType.ALL})
  private Child child;
  // ...
}

Child имеет уникальное поле:

@Entity class Child {
  // ...
  @Column(unique = true)
  private String name;
  // ...
}

Когда мне нужен новый Child , я спрашиваю ChildDAO first:

Child child = childDao.findByName(name);
if(child == null) {
  child = new Child(name);
}

Parent parent = new Parent();
parent.setChild(child);

Проблема в том, что если я сделаю, как указано выше дважды (с тем же именем для Child ), и сохраню только Parent в конце, я получуисключение ограничения.Что кажется нормальным, поскольку изначально в базе данных не было дочерних элементов с указанным именем.

Проблема в том, что я не уверен, что было бы лучшим способом избежать этой ситуации.

Ответы [ 4 ]

10 голосов
/ 28 ноября 2011

Вы создаете два непостоянных экземпляра Child с new Child() дважды, затем помещаете их в двух разных родителей. Когда вы сохраняете родительские объекты, каждый из двух новых дочерних экземпляров будет сохраняться / вставляться посредством каскада, каждый из которых будет иметь свой @Id. Уникальное ограничение на имя затем нарушается. Если вы используете CascadeType.ALL, то каждый раз, когда вы делаете new Child(), вы можете получать отдельный постоянный объект.

Если вы действительно хотите, чтобы два экземпляра Child обрабатывались как один постоянный объект с одним и тем же идентификатором, вам нужно будет сохранить его отдельно, чтобы связать его с контекстом / сеансом постоянства. Последующие вызовы childDao.findByName затем очистят вставку и вернут нового дочернего элемента, которого вы только что создали, так что вы не будете делать new Child() дважды.

7 голосов
/ 28 ноября 2011

Вы получаете это, потому что вы пытаетесь сохранить объект, который уже существует (тот же идентификатор). Ваш каскад, вероятно, не сохраняется, это MERGE / UPDATE / REMOVE / DETACH. Лучший способ избежать этой ситуации - правильно настроить каскад или управлять каскадами вручную. В сущности, каскадирование является обычным виновником.

Вы, вероятно, хотите что-то вроде этого:

//SomeService--I assume you create ID's on persist
saveChild(Parent parent, Child child)
{
  //Adding manually (using your cascade ALL)
  if(child.getId() == null) //I don't exist persist
    persistence.persist(child);
  else
    persistence.merge(child); //I'm already in the DB, don't recreate me

  parent.setChild(child);
  saveParent(parent); //using a similar "don't duplicate me" approach.
}

Каскады могут быть крайне разочаровывающими, если вы позволяете платформе управлять ею, потому что вы иногда пропускаете обновления, если она кэшируется (особенно с отношениями Collections in ManyToOne). Если вы явно не сохраняете родительский объект и не позволяете платформе работать с каскадами. Вообще говоря, я разрешаю Cascade.ALL от моих родителей в отношениях и каскад всего из DELETE / PERSIST от моих детей. Я пользователь Eclipselink, поэтому мой опыт работы с Hibernate / other ограничен, и я не могу говорить с кешированием. * Действительно, подумайте об этом - если вы спасаете ребенка, вы действительно хотите сохранить все объекты, связанные с ним? Кроме того, если вы добавляете ребенка, не должны ли вы уведомить родителя? *

В вашем случае у меня просто был бы метод "saveParent" и "saveChild" в моем Dao / Service, который проверял правильность кэша и базы данных во избежание головной боли. Управление вручную означает, что вы будете иметь абсолютный контроль и вам не придется полагаться на каскады, чтобы выполнять свою грязную работу. В однонаправленном направлении они фантастические, в ту минуту, когда они выходят «вверх по течению», вы можете столкнуться с неожиданным поведением.

4 голосов
/ 28 ноября 2011

Если вы создадите два объекта, и JPA сохранит их автоматически, то вы получите две строки в базе данных. Итак, у вас есть два варианта: не создавать два объекта или не разрешать JPA сохранять их автоматически.

Чтобы не создавать два объекта, вы должны расположить свой код так, что если два родителя попытаются создать дочерние элементы с одинаковыми именами, они получат один и тот же фактический экземпляр. Вы, вероятно, захотите WeakHashMap (ограниченный текущим запросом, возможно, с помощью локальных переменных), с ключом по имени, где родители могут найти имя своего нового ребенка, чтобы увидеть, существует ли ребенок. Если нет, они могут создать объект и поместить его на карту.

Чтобы JPA не сохраняла объекты автоматически, отбросьте каскад и используйте persist , чтобы вручную добавить объекты в контекст сразу после создания.

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

1 голос
/ 28 ноября 2011

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

Когда вы создаете новый объект, он должен управляться entityManager таким же образом, когда вы «находите» объект с помощью DAO, он должен получить регистр из БД и поместить объект в контекст entityManager.

Попробуйте сначала сохранить или объединить дочерний объект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...