Hibernate вставляет дубликаты в коллекцию @OneToMany - PullRequest
19 голосов
/ 26 октября 2011

У меня есть вопрос, касающийся 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;
    }
}

Теперь рассмотрим этот фрагментcode:

// 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 в качестве поставщика сохраняемости код работает так, как ожидалось (без дубликатов).

Это ошибка Hibernate или я что-то упустил?

Спасибо

Ответы [ 5 ]

27 голосов
/ 26 октября 2011

Это ошибка в Hibernate. Удивительно, но пока не сообщается, не стесняйтесь сообщать об этом .

Операции с неинициализированными отложенными коллекциями ставятся в очередь для их выполнения после инициализации сбора, и Hibernate не обрабатывает ситуацию, когда эти операции конфликтуют с данными из базы данных. Обычно это не проблема, поскольку эта очередь очищается на flush(), а возможные конфликтующие изменения распространяются на базу данных также на flush(). Однако некоторые изменения (такие как сохранение сущностей с идентификаторами, сгенерированными генератором типа IDENTITY, я полагаю, это ваш случай) передаются в базу данных без полного flush(), и в этих случаях возможны конфликты. *

В качестве обходного пути вы можете flush() сеанс после сохранения дочернего элемента:

em.persist(child); 
em.flush();
2 голосов
/ 13 апреля 2016

Я сталкивался с этим вопросом, когда у меня были проблемы не с добавлением элементов в список, помеченный @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")
2 голосов
/ 22 января 2015

Я исправил эту проблему, сказав Hibernate не добавлять дубликаты в мою коллекцию. В вашем случае измените тип поля children с List<Child> на Set<Child> и внедрите equals(Object obj) и hashCode() в классе Child.

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

1 голос
/ 15 января 2015

Используя Java Enterprise Context в Wildfly (8.2.0-Final) (я думаю, что это Hibernate версия 4.3.7), я мог обойти эту проблему, чтобы сначала сохранить ребенка и добавить его в коллекцию lazy:

...
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test(){
    Child child = new Child();
    child.setParent(parent);
    childFacade.create(child);

    parent.getChildren().add(cild);

    parentFacade.edit(parent);
}
0 голосов
/ 13 января 2017

Управляем этим, просто вызывая метод empty ().Для этого сценария

parent.getChildren().isEmpty()

до

parent.getChildren().add(child);
...