Eliminar objetos con Hibernate

1. Información general

Como marco ORM con todas las funciones, Hibernate es responsable de la gestión del ciclo de vida de los objetos persistentes (entidades), incluidas las operaciones CRUD como leer , guardar , actualizar y eliminar .

En este artículo, exploramos varias formas en las que los objetos pueden eliminarse de una base de datos usando Hibernate y explicamos problemas comunes y trampas que pueden ocurrir.

Usamos JPA y solo damos un paso atrás y usamos la API nativa de Hibernate para aquellas características que no están estandarizadas en JPA.

2. Diferentes formas de eliminar objetos

Los objetos se pueden eliminar en los siguientes escenarios:

  • Utilizando EntityManager.remove
  • Cuando una eliminación se realiza en cascada desde otras instancias de entidad
  • Cuando se aplica un huérfano
  • Ejecutando una declaración de eliminación de JPQL
  • Ejecutando consultas nativas
  • Aplicando una técnica de eliminación suave (filtrando entidades eliminadas temporalmente por una condición en una cláusula @Where )

En el resto del artículo, analizamos estos puntos en detalle.

3. Eliminación mediante Entity Manager

La eliminación con EntityManager es la forma más sencilla de eliminar una instancia de entidad:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); assertThat(foo, notNullValue()); entityManager.remove(foo); flushAndClear(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); 

En los ejemplos de este artículo, usamos un método auxiliar para limpiar y borrar el contexto de persistencia cuando sea necesario:

void flushAndClear() { entityManager.flush(); entityManager.clear(); }

Después de llamar al método EntityManager.remove , la instancia proporcionada pasa al estado eliminado y la eliminación asociada de la base de datos se produce en el siguiente vaciado.

Tenga en cuenta que la instancia eliminada se vuelve a conservar si se le aplica una operación PERSIST . Un error común es ignorar que una operación PERSIST se ha aplicado a una instancia eliminada (generalmente, porque se conecta en cascada desde otra instancia en el momento de la descarga), porque la sección 3.2.2 de la especificación JPA exige que dicha instancia sea persistió de nuevo en tal caso.

Ilustramos esto definiendo una asociación @ManyToOne de Foo a Bar :

@Entity public class Foo { @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Bar bar; // other mappings, getters and setters }

Cuando eliminamos una instancia de Bar a la que hace referencia una instancia de Foo que también se carga en el contexto de persistencia, la instancia de Bar no se eliminará de la base de datos:

Bar bar = new Bar("bar"); Foo foo = new Foo("foo"); foo.setBar(bar); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); bar = entityManager.find(Bar.class, bar.getId()); entityManager.remove(bar); flushAndClear(); bar = entityManager.find(Bar.class, bar.getId()); assertThat(bar, notNullValue()); foo = entityManager.find(Foo.class, foo.getId()); foo.setBar(null); entityManager.remove(bar); flushAndClear(); assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Si se eliminó el Bar es referenciado por un Foo , el persistir operación se conecta en cascada a partir de Foo a Bar porque la asociación se marca con cascade = CascadeType.ALL y la eliminación se no programadas. Para verificar que esto esté sucediendo, podemos habilitar el nivel de registro de seguimiento para el paquete org.hibernate y buscar entradas, como la eliminación de la entidad sin programar .

4. Eliminación en cascada

La eliminación se puede conectar en cascada a las entidades secundarias cuando se eliminan los padres:

Bar bar = new Bar("bar"); Foo foo = new Foo("foo"); foo.setBar(bar); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); entityManager.remove(foo); flushAndClear(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Aquí se elimina la barra porque la eliminación se realiza en cascada desde foo , ya que la asociación se declara en cascada para todas las operaciones del ciclo de vida de Foo a Bar .

Tenga en cuenta que casi siempre es un error poner en cascada la operación REMOVE en una asociación @ManyToMany , porque eso desencadenaría la eliminación de instancias secundarias que pueden estar asociadas con otras instancias principales. Esto también se aplica a CascadeType.ALL , ya que significa que todas las operaciones se realizarán en cascada, incluida la operación REMOVE .

5. Remoción de huérfanos

La directiva orphanRemoval declara que las instancias de entidades asociadas deben eliminarse cuando se desasocian del padre o, de manera equivalente, cuando se elimina el padre.

Mostramos esto definiendo dicha asociación de Bar a Baz:

@Entity public class Bar { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List bazList = new ArrayList(); // other mappings, getters and setters }

Luego, una instancia de Baz se elimina automáticamente cuando se elimina de la lista de una instancia de Bar principal :

Bar bar = new Bar("bar"); Baz baz = new Baz("baz"); bar.getBazList().add(baz); entityManager.persist(bar); flushAndClear(); bar = entityManager.find(Bar.class, bar.getId()); baz = bar.getBazList().get(0); bar.getBazList().remove(baz); flushAndClear(); assertThat(entityManager.find(Baz.class, baz.getId()), nullValue());

La semántica de la operación orphanRemoval es completamente similar a una operación REMOVE aplicada directamente a las instancias secundarias afectadas , lo que significa que la operación REMOVE se conecta en cascada a los hijos anidados. Como consecuencia, debe asegurarse de que ninguna otra instancia haga referencia a las eliminadas (de lo contrario, se volverán a conservar).

6. Eliminación mediante una declaración JPQL

Hibernate admite operaciones de eliminación de estilo DML:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); entityManager.createQuery("delete from Foo where id = :id") .setParameter("id", foo.getId()) .executeUpdate(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

Es importante tener en cuenta que las declaraciones JPQL estilo DML no afectan ni el estado ni el ciclo de vida de las instancias de entidad que ya están cargadas en el contexto de persistencia , por lo que se recomienda que se ejecuten antes de cargar las entidades afectadas.

7. Eliminación mediante consultas nativas

A veces necesitamos recurrir a consultas nativas para lograr algo que no es compatible con Hibernate o es específico de un proveedor de base de datos. También podemos eliminar datos en la base de datos con consultas nativas:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); entityManager.createNativeQuery("delete from FOO where ID = :id") .setParameter("id", foo.getId()) .executeUpdate(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

La misma recomendación se aplica a las consultas nativas que a las declaraciones de estilo JPA DML, es decir, las consultas nativas no afectan ni al estado ni al ciclo de vida de las instancias de entidad que se cargan en el contexto de persistencia antes de la ejecución de las consultas .

8. Eliminación suave

A menudo, no es deseable eliminar datos de una base de datos por motivos de auditoría y mantenimiento del historial. En tales situaciones, podemos aplicar una técnica llamada eliminaciones suaves. Básicamente, solo marcamos una fila como eliminada y la filtramos cuando recuperamos datos.

In order to avoid lots of redundant conditions in where clauses in all the queries that read soft-deletable entities, Hibernate provides the @Where annotation which can be placed on an entity and which contains an SQL fragment that is automatically added to SQL queries generated for that entity.

To demonstrate this, we add the @Where annotation and a column named DELETED to the Foo entity:

@Entity @Where(clause = "DELETED = 0") public class Foo { // other mappings @Column(name = "DELETED") private Integer deleted = 0; // getters and setters public void setDeleted() { this.deleted = 1; } }

The following test confirms that everything works as expected:

Foo foo = new Foo("foo"); entityManager.persist(foo); flushAndClear(); foo = entityManager.find(Foo.class, foo.getId()); foo.setDeleted(); flushAndClear(); assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

9. Conclusion

In this article, we looked at different ways in which data can be deleted with Hibernate. We explained basic concepts and some best practices. We also demonstrated how soft-deletes can be easily implemented with Hibernate.

La implementación de este Tutorial de eliminación de objetos con Hibernate está disponible en Github. Este es un proyecto basado en Maven, por lo que debería ser fácil de importar y ejecutar como está.