The question title basically says it all. Is it possible in JPA/Hibernate to gracefully prevent the deletion of an entity from the database? What I would like is to flag the entity as "hidden" instead of actually removing it.
I also want the Cascade
semantics to be preserved, such that if I try to delete an entity that owns a collection of some other entity, the owning entity and every entity in its collection get marked as hidden without any extra work being necessary on my part, beyond implementing the @PreRemove
handler that prevents the deletion and marks the entity as hidden.
Is this possible, or do I need to figure out some other approach?
Is it possible in JPA/Hibernate to gracefully prevent the deletion of an entity from the database?
Yes, as long as you avoid using EntityManager.remove(entity)
this is possible. If you do use EntityManager.remove()
, then the JPA provider will flag the object for deletion using a corresponding SQL DELETE statement implying that a elegant solution will not be possible once you flag the object for deletion.
In Hibernate, you can achieve this using @SQLDelete
and @Where
annotations. However, this will not play well with JPA, as EntityManager.find()
is known to ignore the filter specified in the @Where
annotation.
A JPA-only solution would therefore involve, adding a flag i.e. a column, in the Entity classes to distinguish logically deleted entities in the database from "alive" entities. You will need to use appropriate queries (JPQL and native) to ensure that logically deleted entities will not be available in the result sets. You can use the @PreUpdate
and @PrePersist
annotations to hook onto the entity lifecycle events to ensure that the flag is updated on persist and update events. Again, you will need to ensure that you will not invoke the EntityManager.remove
method.
I would have suggested using the @PreRemove
annotation to hook onto the lifecycle event that is triggered for removal of entities, but using an entity listener to prevent deletion is fraught with trouble for the reasons stated below:
SQL DELETE
from occurring in a logical sense, you will need to persist object in the same transaction to recreate it*. The only problem is that it is not a good design decision to reference the EntityManager
in a EntityListener, and by inference invoke EntityManager.persist
in the listener. The rationale is quite simple - you might end up obtaining a different EntityManager reference in the EntityListener and this will only result in vague and confusing behavior in your application.SQL DELETE
in the transaction itself from occurring, then you must throw an Exception in your EntityListener. This usually ends up rolling back the transaction (especially if the Exception is a RuntimeException or an application exception that is declared to be one that causes rollbacks), and does not offer any benefit, for the entire transaction will be rolled back.If you have the option of using EclipseLink instead of Hibernate, then it appears that an elegant solution is possible if you define an appropriate DescriptorCustomizer
or by using the AdditionalCriteria
annotation. Both of these appear to work well with the EntityManager.remove
and EntityManager.find
invocations. However, you might still need to write your JPQL or native queries to account for the logically deleted entities.
* This is outlined in the JPA Wikibook on the topic of cascading Persist:
if you remove an object to have it deleted, if you then call persist on the object, it will resurrect the object, and it will become persistent again. This may be desired if it is intentional, but the JPA spec also requires this behavior for cascade persist. So if you remove an object, but forget to remove a reference to it from a cascade persist relationship, the remove will be ignored.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With