Гибернация @OneToMany с привязкой mappedBy (родитель-потомок) и проблемой кэша
-
05-07-2019 - |
Вопрос
У меня эта проблема уже давно, я искал в Интернете все подряд и пока не нашел решения.Я надеюсь, что вы сможете мне в этом помочь.
У меня есть родительско-дочернее отношение между двумя объектами, подобное следующему:
@Entity
public class Parent {
// ...
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Set<Child> children = new HashSet<Child>();
// ...
}
@Entity
public class Child {
// ...
@ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
// ...
}
Дело в том, что когда я создаю новый дочерний элемент и назначаю его родительскому, родительский элемент не обновляется, если он уже находится в кэше.
Parent parent = new Parent();
em.persist(parent);
// ...
Child child = new Child();
child.setParent(parent);
em.persist(child);
parent.getChildren().size(); // returns 0
Я пытался использовать @preUpdate для автоматического добавления дочернего элемента к родительскому, когда дочерний элемент сохраняется, но в случае, когда у нас есть 2 менеджера сущностей в 2 разных потоках (например, в JBoss), проблема все еще существует, пока мы не вызовем em.refresh(parent)
Итак, вопрос в том, есть ли способ плавно устранить проблему и гарантировать, что parent.getChildren()
всегда возвращать актуальный список дочерних элементов?
Решение
Большинство ORM будут вести себя таким образом.
Объект в кэше не обновляется из базы данных (дополнительное чтение не требуется). Также думайте об объектной модели и постоянстве как об отдельном. т.е. поддерживать вашу объектную модель в соответствии с самим собой и не полагаться на механизм персистентности, чтобы сделать это для вас.
Итак, если вы хотите, чтобы объект был добавлен в коллекцию, сделайте это в " setParent " код.
Лучшая практика в этом случае состоит в том, чтобы одна сторона отношений выполняла всю работу и позволяла другой стороне подчиняться ей. Также я бы предложил использовать доступ к полям, а не доступ к методам, чтобы вы могли гибко настраивать методы.
Добавьте метод к родителю с именем addChild
public void addChild(Child child) {
child.setParent0(this);
getChildren().add(individualNeed);
}
, а затем установите setParent в Child:
public void setParent(Parent parent) {
parent.addChild(child);
}
setParent0 in Child - это свойство stter для parent on child.
public void setParent0(Parent parent) {
this.parent = parent;
}
Я бы также предположил, что " getChildren " метод возвращает неизменяемую коллекцию, чтобы разработчики не использовали этот метод по неосторожности (я научился всему этому нелегко).
Еще одна вещь, у вас должен быть нулевой код проверки и другие защитные элементы в приведенном выше коде, я оставил это для ясности.
Другие советы
Почти уверен, что ваша проблема здесь в ваших каскадных настройках.
@Entity
public class Parent {
// ...
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY,
cascade = {CascadeType.REMOVE, CascadeType.PERSIST})
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private Set<Child> children = new HashSet<Child>();
// ...
}
@Entity
public class Child {
// ...
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private Parent parent;
// ...
}
Использование этих каскадных настроек приведет к каскадному сохранению и обновлению дочерних объектов.
например.
Parent parent = new Parent();
em.persist(parent);
// ...
Child child = new Child();
child.setParent(parent);
em.persist(child); //will cascade update to parent
parent.getChildren().size(); // returns 1
или
Parent parent = new Parent();
Child child = new Child();
parent.setChild(parent);
em.persist(parent); //will cascade update to child
child.getParent(); // returns the parent
более подробную информацию об этом можно найти по адресу Примечания к переходу в спящий режим
Что касается вашей проблемы с кэшированием, то это очень распространенная проблема, когда у вас есть несколько виртуальных машин, работающих с одной и той же базой данных, с отдельными кэшами. Это называется "дрейф кеша". Р>
Большинство реализаций кэша, удобных для использования в спящем режиме (ehcache, OSCache и SwarmCache), имеют встроенный распределенный кеш, который можно использовать для синхронизации кешей. Распределенный кеш, как правило, отправляет многоадресные сообщения, обновляющие состояние кеша. Например, выполнение удаления уровня кэша второго уровня с помощью SessionFactory.evict (Class, id) приведет к тому, что сообщение о недействительности будет отправлено на другие кэши в кластере, что приведет к аннулированию любых других копий этого объекта в других кэшах. Р>
В зависимости от вашего развертывания, многоадресная рассылка может быть или не быть приемлемой для вас. Если это не так, вам может потребоваться использовать решение с одним кэшем, например memcached.
Мне лично показалось, что конфигурация распределенного кеша eh очень проста.
Кэш EH обсуждает проблему более подробно здесь: http://ehcache.org/documentation/ distributed_caching.html р>