Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA/Hibernate - Prevent deletion in PreRemove handler?

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?

like image 657
aroth Avatar asked Jul 11 '11 05:07

aroth


1 Answers

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:

  • If you need to prevent the 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.
  • If you need to prevent the 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.

like image 101
Vineet Reynolds Avatar answered Sep 29 '22 11:09

Vineet Reynolds