2010-10-21 7 views
15

Ich weiß, dass das Löschen verwaister untergeordneter Objekte eine häufige Frage zu SO und ein häufiges Problem für Hibernate-Neulinge ist und dass die relativ standardmäßige Antwort darin besteht, dass Sie eine Variation von cascade=all,delete-orphan oder cascade=all-delete-orphan für die untergeordnete Sammlung haben.Kann Hibernate verwaiste Sammlungen löschen, wenn ein losgelöstes Objekt aktualisiert wird?

Ich möchte in der Lage sein, Hibernate erkennen zu lassen, dass untergeordnete Objekt wurde geleert/aus dem übergeordneten Objekt entfernt und die Zeilen in der unterordneten Tabelle aus der Datenbank gelöscht, wenn das übergeordnete Objekt aktualisiert wird. Zum Beispiel:

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

Meine aktuelle Zuordnung für die Parent Klasse wie folgt aussieht:

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

Dies funktioniert gut für mich, wenn eine angeschlossene Objekt zu aktualisieren, aber ich habe einen Anwendungsfall, in dem wir möchten in der Lage sein, ein losgelöstes Objekt (das von einem Remote-Client über HTTP/JSON an unsere API-Methode gesendet wurde) direkt an die Hibernate-Sitzung zu übergeben, damit Clients das übergeordnete Objekt auf beliebige Weise bearbeiten können sie mögen und haben die Änderungen beibehalten.

Beim Aufruf von session.update(parent) auf mein losgelöstes Objekt sind die Zeilen in der untergeordneten Tabelle verwaist (die FK-Spalte ist auf Null gesetzt), aber nicht gelöscht. Beachten Sie, dass das erste Mal, wenn die Hibernate-Sitzung diese Objektinstanz sieht, wenn ich session.update() aufruft, ich das Objekt nicht erneut mit der Sitzung verknüpfe oder auf andere Weise zusammenführe. Ich verlasse mich darauf, dass der Client Objekte übergibt, deren Bezeichner tatsächlichen Objekten in der Datenbank entsprechen. Zum Beispiel ist die Logik in meinem API-Service-Methode etwas wie folgt aus:

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

Ist es möglich, Hibernate, um verwaiste Kinder Sammlungen in frei stehende Elternobjekten zu erkennen, wenn zu session.update(parent) weitergegeben? Oder missbrauche ich das losgelöste Objekt irgendwie?

Meine Hoffnung war, dass ich jede Art von komplexen Interaktionen mit Hibernate vermeiden konnte, um Änderungen an einer gelösten Instanz zu erhalten. Meine API-Methode muss das gelöste Objekt nach dem Aufruf an session.update(parent) nicht weiter modifizieren. Diese Methode ist lediglich für die dauerhaften Änderungen verantwortlich, die von Remote-Client-Anwendungen vorgenommen werden.

Antwort

1

Ich denke, wenn Sie losgelöste Sitzung verwenden, könnten Sie Probleme mit Sammlungen haben. Ich werde vorschlagen, dass Sie zuerst die Entität mit der Sammlung laden und dann diese Entität mit den Änderungen aktualisieren, die Ihnen helfen werden.

+0

Haben Sie bedeuten, laden Sie die bestehende Einheit und dann 'merge () 'es mit der abgetrennten Instanz an mich weitergegeben API? –

+0

@ Mattb: Sie können eine Logik zum Zusammenführen der Sammlung schreiben, oder auch nur die vorherige Sammlung ersetzen und die neue setzen, aber stellen Sie sicher, dass Sie Tag in Ihrem hbm hinzugefügt haben. Diese enthält eine einfache SQL-Abfrage zum Löschen der gesamten Sammlung vor dem Speichern einer neuen. oder auf andere Weise, wenn Sie etwas hinzufügen, wird diese sql löschen jedes Mal gefeuert und dann wird eine neue Kopie Ihrer Sammlung hinzugefügt. Dies kann Ihr Problem jedes Mal beheben, wenn Sie die Sammlung manuell aktualisieren. –

+0

Ich bin mir nicht sicher, ob mir diese Lösung gefällt, da ich mein Nutzungsmuster ändern müsste - zum Beispiel ''. Hatte gehofft, dass das mit Mapping behoben werden konnte. –

8

Ihre Mapping (vereinfacht)

<?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> 

produziert

PARENT 
    ID 

CHILD 
    CHILD_ID 
    PARENT_ID 

Nach was du gesagt hast

ich Hibernate in der Lage sein möchte das Kind erkennen Sammlung haben wurde entfernt aus das übergeordnete Objekt und gelöscht haben die Zeilen in der untergeordneten Tabelle aus der Datenbank , wenn das übergeordnete Objekt

So etwas wie

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

session.update(parent); 

Sie aktualisiert wird gesagt, es feine funktioniert, weil Sie eine angefügte übergeordnete Instanz

Jetzt sehen wir die folgende (Hinweis Assert.assertNull (Sekunde))

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(); 
    } 
} 

Leider, den Test nicht bestehen. Was du tun kannst ???

  • Aktivieren Sie ein lang laufendes Gespräch

Hibernate Referenz sagt

Extended (oder Long) Session - Die Hibernate Session aus der zugrunde liegenden JDBC-Verbindung getrennt werden kann, nachdem die Datenbanktransaktion hat wurde festgeschrieben und erneut verbunden, wenn eine neue Clientanforderung auftritt. Dieses Muster ist als Session-pro-Konversation bekannt und macht sogar das erneute Anbringen unnötig. Die automatische Versionierung wird verwendet, um gleichzeitige Änderungen zu isolieren, und die Sitzung darf normalerweise nicht automatisch, sondern explizit geleert werden.

Haftungsausschluss: Ich habe kein Szenario, das lang laufende Konversation verwendet. Java EE Stateful-Session-Beans unterstützen eine lang andauernde Konversation. Aber seine Unterstützung ist für JPA (nicht Hibernate)

Oder können Sie eine alternative Zuordnung erstellen, die Ihr Kind als Verbundelemente aktiviert.Weil seine Lebenszyklus auf das übergeordnete Objekt abhängig ist, können Sie sich auf Verbundelemente verlassen, um zu bekommen, was Sie wollen

Erstellen Sie eine Klasse namens AlternativeParent die Eltern

public class AlternativeParent extends Parent {} 

Jetzt seine Mapping (Hinweis Kind als Verbundelement erstreckt anstelle von einfacher @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> 

Jetzt implementieren eine bequeme Methode equals in der Kinderklasse

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 
    } 
} 

Wenn ich den Testfall Refactoring oben gezeigt (jetzt durch AlternativeParent anstelle)

@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(); 

} 

Ich sehe einen grünen Balken

Verwandte Themen