Возможно ли в JPA / Hibernate изящно предотвратить удаление объекта из базы данных?
Да, если вы избегаете использования EntityManager.remove(entity)
, это возможно.Если вы используете EntityManager.remove()
, то поставщик JPA пометит объект для удаления с помощью соответствующего оператора SQL DELETE, подразумевая, что элегантное решение будет невозможно после того, как вы отметите объект для удаления.
В Hibernate,Вы можете достичь этого, используя @SQLDelete
и @Where
аннотации .Однако это не будет хорошо работать с JPA, так как известно, что EntityManager.find()
игнорирует фильтр, указанный в аннотации @Where
.
Поэтому решение только для JPA будет включать добавление флага, то есть столбца,в классах сущностей, чтобы отличить логически удаленные сущности в базе данных от «живых» сущностей.Вам нужно будет использовать соответствующие запросы (JPQL и нативный), чтобы гарантировать, что логически удаленные объекты не будут доступны в наборах результатов.Вы можете использовать аннотации @PreUpdate
и @PrePersist
, чтобы подключиться к событиям жизненного цикла объекта, чтобы обеспечить обновление флага при сохранении и обновлении событий.Опять же, вам нужно убедиться, что вы не будете вызывать метод EntityManager.remove
.
Я бы предложил использовать аннотацию @PreRemove
для привязки к событию жизненного цикла, которое запускается для удаления сущностей, но с использованиемпрослушиватель сущностей для предотвращения удаления сталкивается с проблемами по причинам, указанным ниже:
- Если вам нужно предотвратить появление
SQL DELETE
в логическом смысле, вам нужно будет сохранить объект в том же самом виде.транзакция для его воссоздания *.Единственная проблема заключается в том, что это неправильное проектное решение - ссылаться на EntityManager
в EntityListener и вызывать EntityManager.persist
в слушателе.Обоснование довольно простое - вы можете в конечном итоге получить другую ссылку EntityManager в EntityListener, и это приведет только к неопределенному и запутанному поведению в вашем приложении. - Если вам нужно предотвратить
SQL DELETE
в самой транзакции, так что вы должны выбросить исключение в свой EntityListener.Обычно это завершает откат транзакции (особенно если исключение является RuntimeException или исключением приложения, которое объявлено как вызывающее откат) и не дает никаких преимуществ, поскольку для всей транзакции будет выполнен откат.
Если у вас есть возможность использовать EclipseLink вместо Hibernate, то оказывается, что элегантное решение возможно, если вы определите соответствующий DescriptorCustomizer
или с помощью AdditionalCriteria
аннотация.Похоже, что оба они хорошо работают с вызовами EntityManager.remove
и EntityManager.find
.Тем не менее, вам все равно может потребоваться написать свои JPQL или собственные запросы для учета логически удаленных объектов.
* Это описано в JPA Wikibook по темеиз каскадного Persist :
если вы удаляете объект для его удаления, если вы затем вызываете persist для объекта, он воскресит объект и снова станет постоянным.Это может быть желательным, если оно является преднамеренным, но спецификация JPA также требует, чтобы такое поведение каскадно сохранялось.Поэтому, если вы удалите объект, но забудете удалить ссылку на него из отношения каскадного сохранения, удаление будет проигнорировано.