Ответ 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, удаление будет проигнорировано.