¿Es posible en JPA/Hibernate evitar graciosamente la eliminación de una entidad de la base de datos?
Sí, siempre que evite usar EntityManager.remove(entity)
esto es posible. Si usa EntityManager.remove()
, el proveedor de JPA marcará el objeto para su eliminación utilizando una declaración SQL DELETE correspondiente que implica que una solución elegante no será posible una vez que marque el objeto para su eliminación.
En Hibernate, puede lograrlo usando @SQLDelete
and @Where
annotations. Sin embargo, esto no funcionará bien con JPA, ya que se sabe que EntityManager.find()
ignora el filtro especificado en la anotación @Where
.
Una solución solo de JPA implicaría, por lo tanto, agregar un marcador, es decir, una columna, en las clases de Entidad para distinguir entidades eliminadas lógicamente en la base de datos de entidades "vivas". Tendrá que usar consultas apropiadas (JPQL y nativo) para asegurarse de que las entidades eliminadas lógicamente no estarán disponibles en los conjuntos de resultados. Puede usar las anotaciones @PreUpdate
y @PrePersist
para enganchar en los eventos del ciclo de vida de la entidad para asegurarse de que el indicador se actualice en los eventos de persistencia y actualización. De nuevo, deberá asegurarse de no invocar el método EntityManager.remove
.
que habría sugerido el uso de la anotación @PreRemove
a engancharse en el caso del ciclo de vida que se activa para la eliminación de las entidades, pero utilizando un oyente entidad para evitar el borrado está plagada de problemas por las razones que se indican a continuación:
- Si necesita evitar que ocurra el
SQL DELETE
en un sentido lógico, deberá persistir el objeto en la misma transacción para recrearlo *. El único problema es que no es una buena decisión de diseño hacer referencia al EntityManager
en un EntityListener, y por inferencia invocar EntityManager.persist
en el oyente. El razonamiento es bastante simple: podría obtener una referencia de EntityManager diferente en EntityListener y esto solo dará como resultado un comportamiento vago y confuso en su aplicación.
- Si necesita evitar que se produzca el
SQL DELETE
en la transacción en sí, debe lanzar una excepción en su EntityListener. Esto generalmente termina retrasando la transacción (especialmente si la excepción es una excepción de tiempo de ejecución o una excepción de aplicación que se declara que causa reversiones), y no ofrece ningún beneficio, ya que la transacción completa se retrotraerá.
Si usted tiene la opción de usar EclipseLink en lugar de hibernación, entonces parece que una solución elegante es posible si se define una adecuada DescriptorCustomizer
o utilizando la anotación AdditionalCriteria
. Ambos parecen funcionar bien con las invocaciones EntityManager.remove
y EntityManager.find
. Sin embargo, es posible que aún necesite escribir sus consultas JPQL o nativas para dar cuenta de las entidades eliminadas lógicamente.
* Esto se describe en el JPA Wikibook on the topic of cascading Persist:
Si elimina un objeto para obtener la eliminación, si a continuación, llama a persistir en el objeto, que va a resucitar el objeto, y volverá a ser persistente. Esto puede ser deseado si es intencional, pero la especificación JPA también requiere que este comportamiento persista en cascada. Por lo tanto, si elimina un objeto, pero se olvida de eliminar una referencia de una relación de persistencia en cascada, se ignorará la eliminación.
¿Está intentando imitar la eliminación lógica utilizando JPA? –
@Vineet Reynolds - En esencia, sí. Quiero que se guarden los registros porque la aplicación se sincroniza con los clientes móviles, y debe poder decirles a los clientes qué se ha eliminado. Podría solucionar esto guardando una lista de los identificadores de todo lo que se eliminó en una tabla separada, junto con la marca de tiempo en la que se eliminó, pero preferiría poder anular el comportamiento de eliminación para que solo oculta cosas en vez de eliminarlas. – aroth