Hibernate JPA: @OneToMany удалить старые, вставить новые без флеша
Я действительно никогда не понимал этого поведения в спящем режиме.
Я использую отношения @OneToMany в Entity, называемом "Parent", который аннотируется следующим образом:
@OneToMany(cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval = true)
@JoinColumn(name = "entity_id", insertable = true, updatable = true, nullable = false)
private List<Child> children;
Теперь я хочу сделать следующее за одну транзакцию:
- Получить родительский объект
- итерация по списку детей
- удалить один из дочерних элементов
- добавить новый
Итак, в основном я просто полностью заменяю одного из детей.
Насколько я понимаю эту проблему, я должен был бы сделать что-то вроде этого:
(обратите внимание, что это просто некоторый псевдокод java, чтобы проиллюстрировать проблему)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteAndAdd(Long parentId, Long childId) {
Parent parent = entityManager.find(parentId);
for (Iterator it = parent.children.iterator(); it.hasNext();) {
Child child = it.next();
if (child.id == childId) {
it.remove();
}
}
Child newChild = new Child();
parent.children.add(newChild);
}
Однако это не удается, если новый Child имеет те же уникальные ключевые значения, что и старый. Таким образом, в основном кажется, что старый дочерний объект не удаляется должным образом, прежде чем новый сохраняется.
Если я добавлю entityManager.flush() между удалением старого дочернего элемента и сохранением нового дочернего элемента следующим образом:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteAndAdd(Long parentId, Long childId) {
Parent parent = entityManager.find(parentId);
for (Iterator it = parent.children.iterator(); it.hasNext();) {
Child child = it.next();
if (child.id == childId) {
it.remove();
}
}
entityManager.flush();
Child newChild = new Child();
parent.children.add(newChild);
}
Все работает отлично. Ребенок удаляется до того, как будет вставлен новый.
Поскольку я не хочу, чтобы этот спящий режим смешивал порядок операторов, отправленных в БД, должно быть что-то еще, что я предполагаю о спящем режиме, что не так. Любые идеи, почему последний пример работает, а первый - нет?
Спящий режим версии 3.5.
DB - Mysql InnoDB
Ответы
Ответ 1
Hibernate не знает и не уважает все ограничения базы данных (например, уникальные ограничения MySQL). Это известная проблема, о которой они не планируют в ближайшее время.
Hibernate имеет определенный порядок для способа работы во время флеша.
Устранение сущностей всегда будет происходить после вставок. Единственные ответы, которые я знаю, - удалить ограничение или добавить дополнительный флеш.
EDIT: Кстати, причина для определенного порядка заключается в том, что это единственный способ гарантировать ограничения внешних ключей (одно из ограничений, о которых они заботятся) не нарушаются, даже если пользователь делает что-то из порядок.
Ответ 2
Для будущих читателей одним из способов решения этой проблемы является использование отложенных ограничений. PostgreSQL и Oracle поддерживают их, может быть, и другие СУБД. Hibernate выдает все заявления в транзакции, а отсрочка гарантирует, что ограничения выполняются только при совершении транзакции. В PostgreSQL, например:
ALTER TABLE company
ADD CONSTRAINT name_unique UNIQUE (name) DEFERRABLE INITIALLY DEFERRED;
Он не идеален, но он прост и эффективен.