Подтвердить что ты не робот

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

4b9b3361

Ответ 1

Hibernate не знает и не уважает все ограничения базы данных (например, уникальные ограничения MySQL). Это известная проблема, о которой они не планируют в ближайшее время.

Hibernate имеет определенный порядок для способа работы во время флеша.

Устранение сущностей всегда будет происходить после вставок. Единственные ответы, которые я знаю, - удалить ограничение или добавить дополнительный флеш.

EDIT: Кстати, причина для определенного порядка заключается в том, что это единственный способ гарантировать ограничения внешних ключей (одно из ограничений, о которых они заботятся) не нарушаются, даже если пользователь делает что-то из порядок.

Ответ 2

Для будущих читателей одним из способов решения этой проблемы является использование отложенных ограничений. PostgreSQL и Oracle поддерживают их, может быть, и другие СУБД. Hibernate выдает все заявления в транзакции, а отсрочка гарантирует, что ограничения выполняются только при совершении транзакции. В PostgreSQL, например:

ALTER TABLE company
    ADD CONSTRAINT name_unique UNIQUE (name) DEFERRABLE INITIALLY DEFERRED;

Он не идеален, но он прост и эффективен.