tipos one many fetchtype cascadetype annotation all java hibernate hibernate-cascade

java - many - ¿Puede Hibernate eliminar colecciones huérfanas al actualizar un objeto separado?



spring one to many cascade (2)

Sé que eliminar objetos secundarios huérfanos es una pregunta común en SO y un problema común para las personas nuevas en Hibernate, y que la respuesta bastante estándar es garantizar que tenga alguna variación de cascade=all,delete-orphan o cascade=all-delete-orphan en la colección secundaria.

Me gustaría poder hacer que Hibernate detecte que la colección secundaria se ha vaciado / eliminado del objeto principal y que las filas de la tabla secundaria se eliminen de la base de datos cuando se actualice el objeto principal. Por ejemplo:

Parent parent = session.get(...); parent.getChildren().clear(); session.update(parent);

Mi mapeo actual para la clase Parent ve así:

<bag name="children" cascade="all-delete-orphan"> <key column="parent_id" foreign-key="fk_parent_id"/> <one-to-many class="Child"/> </bag>

Esto funciona bien para mí cuando actualizo un objeto adjunto, pero tengo un caso de uso en el que nos gustaría poder tomar un objeto separado (que ha sido enviado a nuestro método API por un cliente remoto a través de HTTP / JSON), y páselo directamente a la sesión de hibernación, para permitir que los clientes puedan manipular el objeto principal de la forma que deseen y que los cambios se conserven.

Al llamar a session.update(parent) en mi objeto separado, las filas en la tabla secundaria se quedan huérfanas (la columna FK se establece en nula) pero no se eliminan. Tenga en cuenta que cuando llamo a session.update() , esta es la primera vez que Hibernate Session ve esta instancia de objeto: no estoy volviendo a adjuntar o fusionar el objeto con la Session de ninguna otra manera. Confío en que el cliente pase objetos cuyos identificadores correspondan a objetos reales en la base de datos. Por ejemplo, la lógica en mi método de servicio API es algo como esto:

String jsonString = request.getParameter(...); Parent parent = deserialize(jsonString); session.update(parent);

¿Es posible que Hibernate detecte colecciones de niños huérfanos en objetos principales separados cuando se pasa a session.update(parent) ? ¿O estoy usando mal el objeto separado de alguna manera?

Mi esperanza era poder evitar cualquier tipo de interacción compleja con Hibernate para persistir los cambios en una instancia separada. Mi método de API no tiene necesidad de modificar aún más el objeto separado después de la llamada a session.update(parent) , este método es el único responsable de la persistencia de los cambios realizados por las aplicaciones cliente remotas.


Creo que, cuando se usa una sesión separada, es posible que tenga problemas con las colecciones. Le sugeriré que primero cargue la entidad con la colección, y luego actualice esa entidad con los cambios, eso ayudará.


Tu mapeo (simplificado)

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="br.com._3988215.model.domain"> <class name="Parent" table="PARENT"> <id name="id"> <generator class="native"/> </id> <bag cascade="all,delete-orphan" name="childList"> <key column="PARENT_ID" not-null="false"/> <one-to-many class="Child"/> </bag> </class> <class name="Child" table="CHILD"> <id name="id" column="CHILD_ID"> <generator class="native"/> </id> </class> </hibernate-mapping>

produce

PARENT ID CHILD CHILD_ID PARENT_ID

De acuerdo a lo que dijiste

Me gustaría poder hacer que Hibernate detecte que la colección secundaria se ha eliminado del objeto principal y que las filas de la tabla secundaria se eliminen de la base de datos cuando se actualice el objeto principal

Algo como

Parent parent = session.get(...); parent.getChildren().clear(); session.update(parent);

Usted dijo que funciona bien porque tiene una instancia de Padre adjunta

Ahora veamos el siguiente (Aviso Assert.assertNull (segundo) )

public class WhatYouWantTest { private static SessionFactory sessionFactory; private Serializable parentId; private Serializable firstId; private Serializable secondId; @BeforeClass public static void setUpClass() { Configuration c = new Configuration(); c.addResource("mapping.hbm.3988215.xml"); sessionFactory = c.configure().buildSessionFactory(); } @Before public void setUp() throws Exception { Parent parent = new Parent(); Child first = new Child(); Child second = new Child(); Session session = sessionFactory.openSession(); session.beginTransaction(); parentId = session.save(parent); firstId = session.save(first); secondId = session.save(second); parent.getChildList().add(first); parent.getChildList().add(second); session.getTransaction().commit(); session.close(); } @Test public void removed_second_from_parent_remove_second_from_database() { Parent parent = new Parent(); parent.setId((Integer) parentId); Child first = new Child(); first.setId((Integer) firstId); /** * It simulates the second one has been removed */ parent.getChildList().add(first); Session session = sessionFactory.openSession(); session.beginTransaction(); session.update(parent); session.getTransaction().commit(); session.close(); session = sessionFactory.openSession(); session.beginTransaction(); Child second = (Child) session.get(Child.class, secondId); Assert.assertNull(second); session.getTransaction().commit(); session.close(); } }

Lamentablemente , la prueba no pasa. Lo que puedes hacer ???

  • Habilitar una conversación de larga duración

Hibernate referencia dice

Sesión extendida (o larga): la sesión de hibernación se puede desconectar de la conexión JDBC subyacente después de que la transacción de la base de datos se haya confirmado y se vuelva a conectar cuando ocurra una nueva solicitud de cliente. Este patrón se conoce como sesión por conversación y hace que incluso la reincorporación sea innecesaria . El control de versiones automático se usa para aislar modificaciones concurrentes y, por lo general, la sesión no se puede vaciar automáticamente, sino explícitamente.

descargo de responsabilidad: no tengo ningún escenario que utilice una conversación de larga duración. Los beans de sesión con estado de Java EE admiten conversaciones de larga duración. Pero su soporte es para JPA (no Hibernate).

O puede crear un mapeo alternativo que habilite a su hijo como elementos compuestos. Debido a que su ciclo de vida depende del objeto principal, puede confiar en elementos compuestos para obtener lo que desea

Cree una clase llamada AlternativeParent que amplíe Parent

public class AlternativeParent extends Parent {}

Ahora su mapeo (Note Child como elemento compuesto en lugar de simple @Entity)

<class name="AlternativeParent" table="PARENT"> <id name="id"> <generator class="native"/> </id> <bag name="childList" table="CHILD"> <key column="PARENT_ID" not-null="false"/> <composite-element class="Child"> <property column="CHILD_ID" name="id"/> </composite-element> </bag> </class>

Ahora implementa un método de iguales conveniente en la clase Child

public boolean equals(Object o) { if (!(o instanceof Child)) return false; Child other = (Child) o; // identity equality // Used by composite elements if(getId() != null) { return new EqualsBuilder() .append(getId(), other.getId()) .isEquals(); } else { // object equality } }

Si refactorizo ​​el caso de prueba que se muestra arriba (ahora usando AlternativeParent en su lugar)

@Test public void removed_second_from_parent_remove_second_from_database() { AlternativeParent parent = new AlternativeParent(); parent.setId((Integer) parentId); Child first = new Child(); first.setId((Integer) firstId); /** * It simulates the second one has been removed */ parent.getChildList().add(first); Session session = sessionFactory.openSession(); session.beginTransaction(); session.update(parent); session.getTransaction().commit(); session.close(); session = sessionFactory.openSession(); session.beginTransaction(); Child second = (Child) session.get(Child.class, secondId); Assert.assertNull(second); session.getTransaction().commit(); session.close(); }

Veo una barra verde