Hibernate вставляет дубликаты в коллекцию @OneToMany
у меня есть вопрос относительно Hibernate 3.6.7 и JPA 2.0.
рассмотрим следующие сущности (некоторые геттеры и сеттеры опущены для краткости):
@Entity
public class Parent {
@Id
@GeneratedValue
private int id;
@OneToMany(mappedBy="parent")
private List<Child> children = new LinkedList<Child>();
@Override
public boolean equals(Object obj) {
return id == ((Parent)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private int id;
@ManyToOne
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
@Override
public boolean equals(Object obj) {
return id == ((Child)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
Теперь рассмотрим этот кусок кода:
// persist parent entity in a transaction
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = new Parent();
em.persist(parent);
int id = parent.getId();
em.getTransaction().commit();
em.close();
// relate and persist child entity in a new transaction
em = emf.createEntityManager();
em.getTransaction().begin();
parent = em.find(Parent.class, id);
// *: parent.getChildren().size();
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
em.persist(child);
System.out.println(parent.getChildren()); // -> [Child@1, Child@1]
em.getTransaction().commit();
em.close();
дочерний объект ошибочно вставляется дважды в список дочерних объектов родительского объекта.
при выполнении одного из следующих действий код работает нормально (нет дубликатов записей в списке):
- удалить
mappedBy
атрибут в Родительском объекте - выполнить некоторые операции чтения на список детей (например, раскомментируйте строки, отмеченные
*
)
это, очевидно, очень странное поведение. Кроме того, при использовании EclipseLink в качестве поставщика сохраняемости код работает так, как ожидалось (без дубликатов).
это ошибка спящего режима или я что-то пропустил?
спасибо
5 ответов
это ошибка в спящем режиме. Удивительно, но это еще не сообщается,Не стесняйтесь сообщать об этом.
операции против неинициализированных ленивых коллекций помещаются в очередь, чтобы выполнить их после инициализации коллекции, и Hibernate не обрабатывает ситуацию, когда эти операции конфликтуют с данными из базы данных. Обычно это не проблема, потому что эта очередь очищается по flush()
, и возможные противоречивые изменения в базу данных по flush()
Как хорошо. Однако некоторые изменения (например, сохранение сущностей с идентификаторами, генерируемыми генератором типа IDENTITY
, Я думаю, это ваш случай) передаются в базу данных без полного flush()
, и в этих случаях возможны конфликты.
в качестве обходного пути вы можете flush()
сеанс после сохранения ребенка:
em.persist(child);
em.flush();
я исправил эту проблему, сказав Hibernate не добавлять дубликаты в мою коллекцию. В вашем случае измените тип вашего
я столкнулся с этим вопросом, когда у меня были проблемы не с добавлением элементов в список, аннотированный @OneToMany, а при попытке перебирать элементы такого списка. Элементы в списке всегда дублировались, иногда намного больше, чем дважды. (Также произошло, когда аннотировано с @ManyToMany). Использование набора не было решением здесь, так как эти списки должны были позволить дублировать элементы в них.
пример:
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
@Cascade(CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private List entities;
как оказалось, Hibernate выполняет операторы sql, использующие left outer join
, что может привести к дублированию результатов, возвращаемых БД. Что помогло, так это просто определить порядок в результате, используя OrderColumn:
@OrderColumn(name = "columnName")
используя Java enterprise context в Wildfly (8.2.0-Final) (я думаю, что это Hibernate версии 4.3.7) обходной путь для меня был, чтобы сначала сохранить ребенка и добавить его в ленивую коллекцию:
...
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test(){
Child child = new Child();
child.setParent(parent);
childFacade.create(child);
parent.getChildren().add(cild);
parentFacade.edit(parent);
}
удалось это, просто вызвав метод empty (). Для этого сценария
parent.getChildren().isEmpty()
до
parent.getChildren().add(child);