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

JPA/Hibernate - предотвратить удаление в обработчике PreRemove?

Название вопроса в основном говорит все. Возможно ли в JPA/Hibernate грациозно предотвратить удаление объекта из базы данных? Я хотел бы отметить объект как "скрытый", а не удалять его.

Я также хочу, чтобы семантика Cascade была сохранена, так что если я попытаюсь удалить сущность, которая владеет коллекцией какого-либо другого объекта, то владелец и каждый объект в своей коллекции будут помечены как скрытые без дополнительной работы с моей стороны, за пределами реализации обработчика @PreRemove, который предотвращает удаление и помещает объект как скрытый.

Возможно ли это, или мне нужно выяснить какой-то другой подход?

4b9b3361

Ответ 1

Возможно ли в JPA/Hibernate грациозно предотвратить удаление объекта из базы данных?

Да, если вы избегаете использования EntityManager.remove(entity), это возможно. Если вы используете EntityManager.remove(), поставщик JPA будет помечать объект для удаления с помощью соответствующего оператора SQL DELETE, подразумевая, что элегантное решение не будет возможно после того, как вы отметите объект для удаления.

В Hibernate вы можете достичь этого, используя @SQLDelete и @Where аннотации. Однако это плохо работает с JPA, поскольку EntityManager.find(), как известно, игнорирует фильтр, указанный в аннотации @Where.

Таким образом, решение JPA-only предполагает добавление флага, то есть столбца, в классы Entity, чтобы отличать логически удаленные объекты в базе данных от "живых" объектов. Вам нужно будет использовать соответствующие запросы (JPQL и native), чтобы гарантировать, что логически удаленные объекты не будут доступны в наборах результатов. Вы можете использовать аннотации @PreUpdate и @PrePersist, чтобы подключиться к событиям жизненного цикла объекта, чтобы гарантировать, что флаг обновлен при сохранении и обновлении событий. Опять же, вам нужно будет убедиться, что вы не будете использовать метод EntityManager.remove.

Я бы предложил использовать аннотацию @PreRemove для привязки к событию жизненного цикла, которое запускается для удаления сущностей, но использование прослушивателя сущности для предотвращения удаления чревато ошибкой по причинам, указанным ниже:

  • Если вам нужно предотвратить появление SQL DELETE в логическом смысле, вам нужно будет сохранить объект в той же транзакции, чтобы воссоздать его *. Единственная проблема заключается в том, что неплохое конструктивное решение ссылаться на EntityManager в EntityListener, а по умозаключению вызывает EntityManager.persist в слушателе. Обоснование довольно просто - вы можете получить другую ссылку EntityManager в EntityListener, и это приведет только к неопределенному и запутанному поведению в вашем приложении.
  • Если вам нужно предотвратить SQL DELETE в самой транзакции, вы должны выбросить исключение в свой EntityListener. Обычно это приводит к откату транзакции (особенно если Exception является RuntimeException или исключение приложения, которое объявлено как та, которое вызывает откаты) и не приносит никакой пользы, поскольку вся транзакция будет откат.

Если у вас есть возможность использовать EclipseLink вместо Hibernate, то кажется, что изящное решение возможно, если вы определите подходящий DescriptorCustomizer или с помощью аннотации AdditionalCriteria. Обе эти функции хорошо работают с вызовами EntityManager.remove и EntityManager.find. Тем не менее, вам все равно придется писать ваши JPQL или собственные запросы для учета логически удаленных объектов.


* Это описано в JPA Wikibook по теме каскадирования Persist:

если вы удалите объект для его удаления, если вы затем вызовете упор на объект, он воскресит объект, и он снова станет постоянным. Это может быть желательно, если оно преднамеренное, но спецификация JPA также требует такого поведения для сохранения каскада. Поэтому, если вы удаляете объект, но забудьте удалить ссылку на него из отношения cascade persist, удаление будет проигнорировано.