2010-10-21 14 views
15

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.¿Puede Hibernar eliminar colecciones huérfanas al actualizar un objeto separado?

Me gustaría poder hacer que Hibernate detecte que la colección secundaria ha sido vaciada/eliminada del objeto principal, y que las filas en la tabla secundaria hayan sido eliminadas de la base de datos cuando se actualice el objeto principal. Por ejemplo:

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

Mi asignación actual para la clase Parent parece:

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

Esto funciona muy bien para mí cuando se actualiza un objeto unido, 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 pasarlo directamente a la sesión de Hibernate, para permitir a los clientes poder manipular el objeto principal de cualquier forma les gusta y tienen los cambios persistidos.

Al llamar al session.update(parent) en mi objeto separado, las filas en la tabla secundaria quedan huérfanas (la columna FK se establece en nula) pero no se eliminan. Tenga en cuenta que cuando llamo al session.update(), esta es la primera vez que la sesión de Hibernate ve esta instancia de objeto: no estoy volviendo a conectar ni fusionar el objeto con la sesión de ninguna otra manera. Confío en que el cliente pase objetos cuyos identificadores corresponden 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 para detectar colecciones de los niños huérfanos de objetos padres separados cuando se pasa a session.update(parent)? ¿O estoy mal usando el objeto separado de alguna manera?

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

Respuesta

1

Creo que, al utilizar la sesión independiente, es posible que tenga problemas con las colecciones. Le sugiero que primero cargue la entidad con colección, y luego actualice esa entidad con los cambios, eso ayudará.

+0

Qué quiere decir, cargar la entidad existente y luego MERGE () 'con la instancia separada me pasó API? –

+0

@ mattb: puede escribir una lógica para combinar la colección, o incluso simplemente reemplazar la colección anterior y establecer la nueva, pero asegúrese de haber agregado la etiqueta en su hbm. Este contendrá una consulta simple de eliminación de SQL para eliminar la colección completa antes de guardar una nueva. o de otra manera, donde quiera que agregue algo, esta eliminación sql se activará cada vez y luego se agregará una nueva copia de su colección. Esto puede resolver su problema de actualizar la colección de forma manual. –

+0

No estoy seguro si me gusta esta solución, ya que requeriría cambiar mi patrón de uso, agregando '' por ejemplo. Esperaba que esto se pudiera arreglar con el mapeo solo. –

8

Su asignación (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 

Según lo que ha dicho

me gustaría ser capaz de tener Hibernate detectar que la colección infantil ha sido eliminado de el objeto primario, y tienen la filas de la tabla secundaria borrado de la base de datos cuando el objeto primario se actualiza

Algo así como

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

session.update(parent); 

Dijiste que funciona bien porque tiene una instancia principal 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(); 
    } 
} 

Desafortunadamente, la prueba no pasan. Lo que puedes hacer ???

  • Habilitar una conversación de larga duración

referencia Hibernate dice

extendido (o largo) Sesión - La sesión de Hibernate puede ser desconectada de la conexión JDBC subyacente después de la transacción de base de datos tiene se ha confirmado y vuelto a conectar cuando se produce una nueva solicitud del cliente. Este patrón se conoce como sesión-por-conversación y hace que incluso la reinserción sea innecesaria. El control automático de versiones se utiliza para aislar modificaciones simultáneas y, por lo general, no se permite que la sesión se vacíe automáticamente, sino explícitamente.

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

O puede crear una asignación alternativa que habilite a su Niño como elementos compuestos.Debido a su ciclo de vida depende del objeto padre, puede depender de elementos compuestos para conseguir lo que quiere

Crear una clase llamada AlternativeParent que se extiende Padres

public class AlternativeParent extends Parent {} 

Ahora su asignación (Aviso Niño como elemento compuesto en lugar de @Entity llanura)

<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 implementar un cómodo es igual método en la clase del niño

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 i refactorizar el caso de prueba se muestra arriba (ahora mediante el uso de AlternativeParent 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

Cuestiones relacionadas