2014-07-03 12 views
6

Arbeiten mit JPA 1 (Hibernate-Core-Version 3.3.0.SP1 und Hibernate-Entitymanager Version 3.4.0.GA): Ich habe einige Entitäten ähnlich denen unten definiert, wo ChildOne und ChildTwo erstreckt das Vater-Wesen.JPA-Vererbung

Sagen wir, ich habe eine Entität, die ein Vaterelement hat, und eine andere, die eine Sammlung von Vaterelementen hat. In beiden Fällen sollten die Kinder-Entitäten gehen.

@Entity 
@Table(name = "TABLE_ONE") 
public class OneTable { 

@JoinColumn(name = "ID_PK", referencedColumnName = "ID_PK", nullable = false) 
@ManyToOne(optional = false, fetch = FetchType.LAZY) 
private Father element; 
    ... 
} 

@Entity 
@Table(name = "TABLE_ANOTHER") 
public class Another { 

@Fetch(FetchMode.JOIN) 
@OneToMany(cascade = CascadeType.ALL, mappedBy = "id", fetch = FetchType.LAZY) 
private Collection<Father> elementCollection; 

    ... 
} 

Ich erwarte Elemente immer die Kinder zu bekommen, aber wenn ich das Element erhalten getElement() den Vater Element und auf der anderen Seite zurückkehrt, wenn ich die Sammlung getElementCollection() die Kinder Elemente bekommen kommen.

Anscheinend ist die @JoinColumn die Ursache für dieses Verhalten, die Verbindung mit der Vater-Tabelle zu tun und die Kinder-Elemente zu vergessen. Die Sammlung funktioniert wie erwartet.

Wie kann ich das Kinderelement mit einem getElement() Anruf erhalten? Irgendwelche Ideen oder Workarround? Vielen Dank im Voraus.

Antwort

2

Das Problem wird nicht von @JoinColumn verursacht. Der Grund ist Lazy Loading. Ich schaffe es, Ihr Problem in einfacheren Beispiel zu lokalisieren. Verzeihen Sie mir, um die Konvention vom Vater zum Elternteil zu ändern.

Im folgenden Beispiel ist nicht initialisiertes Element der Typ jpa.inheritance.issue.Parent_$$_javassist_1. Es ist ein Hibernate Proxy - dynamisch erstellte Unterklasse von Parent. Sie können es "unproxy", indem Sie Hibernate proprietäre API getHibernateLazyInitializer().getImplementation() aufrufen.

Sammlung von elementCollection ist auch Lazy Initialized. Der Typ der Sammlung ist org.hibernate.collection.PersistentBag, der zum Zeitpunkt des ersten Zugriffs mit korrekten Daten initialisiert wird. Die Sammlung wird auf einmal initialisiert. Bitte beachten Sie den Test, der mit Ihrer genauen Version von Hibernate (3.3.0.SP1/3.4.0.GA) erfolgreich bestanden wurde.

@Test 
    public void test() { 
     Child c = new Child(); 
     em.persist(c); 

     Another a = new Another(); 
     a.setElement(c); 
     Collection<Parent> col = new ArrayList<Parent>(); 
     col.add(c); 
     a.setElementCollection(col); 
     em.persist(a); 
     c.setAnother(a); 

     long idx = a.getId(); 
     tx.commit(); 

     // I'm cleaning the cache to be sure that call to a.getElement() will return proxy. 
     em.clear(); 
     tx = em.getTransaction(); 
     tx.begin(); 

     a = em.find(Another.class, idx); 
     Assert.assertNotNull(a); 
     Parent p = a.getElement(); 
     // At this point p is a type of jpa.inheritance.issue.Parent_$$_javassist_1 

     Assert.assertTrue(p instanceof Parent); 
     Assert.assertFalse(p instanceof Child); 

     // At this point a.elements is a not initialized (empty) collection of type org.hibernate.collection.PersistentBag 
     // When we access this collection for the first time, records are read from the database 
     Assert.assertEquals(1, a.getElementCollection().size()); 

     if (p instanceof HibernateProxy) { 
      p = 
        (Parent) ((HibernateProxy) p).getHibernateLazyInitializer() 
          .getImplementation(); 
     } 

     // At this point p is a type of jpa.inheritance.issue.Child 
     Assert.assertTrue(p instanceof Child); 
    } 

    @Entity 
    public class Another { 

     @JoinColumn(name = "element_id", referencedColumnName = "id", nullable = false) 
     @ManyToOne(fetch=FetchType.LAZY) 
     private Parent element; 
     public Parent getElement() { 
      return element; 
     } 

     public void setElement(Parent element) { 
      this.element = element; 
     } 

     @OneToMany(cascade = CascadeType.ALL, mappedBy = "another", fetch = FetchType.LAZY) 
     public Collection<Parent> elements; 

     public Collection<Parent> getElementCollection() { 
      return elements; 
     } 

     public void setElementCollection(Collection<Parent> elementCollection) { 
      this.elements = elementCollection; 
     } 

     // @Id ... 
    } 

    @Entity 
    @Inheritance(strategy = InheritanceType.JOINED) 
    public class Parent { 
     @ManyToOne 
     private Another another; 

     public Another getAnother() { 
      return another; 
     } 

     public void setAnother(Another another) { 
      this.another = another; 
     } 

     // @Id ... 
    } 

    @Entity 
    public class Child extends Parent {   
    } 

Sie brauchen nicht @DiscriminatorColumn noch @DiscriminatorValue weil diese Anmerkungen mit InheritanceType.SINGLE_TABLE als einzigen Ort benötigt werden, um den Typen zu bestimmen. Mit InheritanceType.JOINED kann Hibernate den polymorphen Typ bestimmen, indem überprüft wird, ob es einen Datensatz in beiden (Eltern und Kind) Tabellen mit der gleichen ID gibt. Sie können die Ruhezustandsprotokollierung aktivieren, um zu sehen, wie die Abfrage zum Ermitteln des Typs aussieht. Es funktioniert wie folgt:

select 
    another0_.id as id0_1_, 
    another0_.element_id as element2_0_1_, 
    parent1_.id as id1_0_, 
    parent1_1_.name as name2_0_, 
    case 
     when parent1_1_.id is not null then 1 
     when parent1_.id is not null then 0 
     else -1 
    end as clazz_0_ 
from 
    Another another0_ 
inner join 
    Parent parent1_ 
     on another0_.element_id=parent1_.id 
left outer join 
    Child parent1_1_ 
     on parent1_.id=parent1_1_.id 
where 
    another0_.id=? 
+0

Guter Punkt. Lassen Sie mich ein paar Dinge ausprobieren und ich werde Sie wissen lassen ... – elcadro

+0

Nur ein paar Anmerkungen zu Ihrer Antwort ... Soweit ich weiß, erfordert die Spezifikation keine Implementierung, um Diskriminator-Spalten zu verwenden, um JOINED-Vererbung zu implementieren. die Annahme ist, dass, wenn @DiscriminatorColumn angegeben ist, es verwendet werden würde ... Auf der anderen Seite, obwohl Ihre Lösung tatsächlich funktioniert, noch einige Zweifel haben. Wenn Sie wissen, dass die Lazyness Proxys zurückgibt, warum gibt ManyToOne einen Proxy an den übergeordneten (jpa.inheritance.issue.Parent _ $$ _ javassist_1) und die OneToMany-Auflistung untergeordnete Proxys (jpa.inheritance.issue.ChildOne _ $$ _ javassist_1) zurück? – elcadro

+0

Der genaue Rückgabetyp des Aufrufs 'getElement()' in einer realen Anwendung ist nicht deterministisch.Wenn das Objekt im Cache der ersten oder zweiten Ebene gefunden wird, wird "Kind" eingegeben. Deshalb rufe ich 'em.clean()' in der Mitte auf - um das spezifische Verhalten zu bestimmen. Aus diesem Grund ist es am besten, in Situationen mit Eltern-Kind-Hierarchie typunabhängig zu sein. Wenn Sie wirklich einen korrekten Typ benötigen, können Sie "unproxy" wie ich vorgeschlagen habe oder besser FetchType zu EAGER ändern (als "unproxying" laden Sie die Beziehung trotzdem). – zbig