2010-08-03 4 views

Antwort

128

Ja, das ist möglich. Dies ist ein Spezialfall der standardmäßigen bidirektionalen Beziehung @ManyToOne/@OneToMany. Es ist besonders, weil die Entität an jedem Ende der Beziehung die gleiche ist. Der allgemeine Fall ist in Abschnitt 2.10.2 der JPA 2.0 spec beschrieben.

Hier ist ein funktionierendes Beispiel. Zunächst wird die Entitätsklasse A:

@Entity 
public class A implements Serializable { 

    @Id 
    @GeneratedValue(strategy=GenerationType.AUTO) 
    private Long id; 
    @ManyToOne 
    private A parent; 
    @OneToMany(mappedBy="parent") 
    private Collection<A> children; 

    // Getters, Setters, serialVersionUID, etc... 
} 

Hier eine grobe main() Methode ist, dass drei solcher Einheiten bestehen:

public static void main(String[] args) { 

    EntityManager em = ... // from EntityManagerFactory, injection, etc. 

    em.getTransaction().begin(); 

    A parent = new A(); 
    A son  = new A(); 
    A daughter = new A(); 

    son.setParent(parent); 
    daughter.setParent(parent); 
    parent.setChildren(Arrays.asList(son, daughter)); 

    em.persist(parent); 
    em.persist(son); 
    em.persist(daughter); 

    em.getTransaction().commit(); 
} 

In diesem Fall werden alle drei Entitätsinstanzen müssen beibehalten werden vor der Transaktion zu begehen. Wenn es mir nicht gelingt, eine der Entitäten im Diagramm der Parent-Child-Beziehungen beizubehalten, wird eine Ausnahme auf commit() ausgelöst. Auf Eclipselink ist dies ein RollbackException, der die Inkonsistenz beschreibt.

Dieses Verhalten kann über das cascade-Attribut unter A@OneToMany und @ManyToOne Annotationen konfiguriert werden. Zum Beispiel, wenn ich cascade=CascadeType.ALL auf diese beiden Anmerkungen setze, könnte ich sicher eine der Entitäten beibehalten und die anderen ignorieren. Angenommen, ich habe in meiner Transaktion parent beibehalten. Die JPA-Implementierung durchläuft die Eigenschaft parentchildren, weil sie mit CascadeType.ALL markiert ist. Die JPA-Implementierung findet son und daughter dort. Es beharrt dann beide Kinder in meinem Namen, obwohl ich es nicht ausdrücklich angefordert habe.

Noch ein Hinweis. Es liegt in der Verantwortung des Programmierers, beide Seiten einer bidirektionalen Beziehung zu aktualisieren. Mit anderen Worten: Wenn ich einem Elternteil ein Kind hinzufüge, muss ich die Elterneigenschaft des Kindes entsprechend aktualisieren. Das Aktualisieren nur einer Seite einer bidirektionalen Beziehung ist ein Fehler unter JPA. Aktualisieren Sie immer beide Seiten der Beziehung. Diese schriftlichen eindeutig auf Seite 42 der JPA 2.0-Spezifikation:

Beachten Sie, dass es die Anwendung ist, die Verantwortung für die Aufrechterhaltung der Konsistenz der Laufzeitbeziehungen, zum Beispiel trägt, um sicherzustellen, daß die „Eins“ und die „n "Seiten einer bidirektionalen Beziehung sind konsistent, wenn die Anwendung die -Beziehung zur Laufzeit aktualisiert.

+0

Vielen Dank für die detaillierte Erklärung!Das Beispiel ist auf den Punkt und arbeitete im ersten Lauf. – sanjayav

+0

@sunnyj Froh zu helfen. Viel Glück mit Ihrem (Ihren) Projekt (en). –

+0

Dieses Problem wurde bereits beim Erstellen von Kategorieentitäten mit Unterkategorien behoben. Es ist hilfreich! –

5

Für mich war der Trick, viele-zu-viele-Beziehung zu verwenden. Angenommen, Ihre Entität A ist eine Division, die Unterteilungen haben kann. Dann (irrelevante Details Überspringen):

@Entity 
@Table(name = "DIVISION") 
@EntityListeners({ HierarchyListener.class }) 
public class Division implements IHierarchyElement { 

    private Long id; 

    @Id 
    @Column(name = "DIV_ID") 
    public Long getId() { 
     return id; 
    } 
    ... 
    private Division parent; 
    private List<Division> subDivisions = new ArrayList<Division>(); 
    ... 
    @ManyToOne 
    @JoinColumn(name = "DIV_PARENT_ID") 
    public Division getParent() { 
     return parent; 
    } 

    @ManyToMany 
    @JoinTable(name = "DIVISION", joinColumns = { @JoinColumn(name = "DIV_PARENT_ID") }, inverseJoinColumns = { @JoinColumn(name = "DIV_ID") }) 
    public List<Division> getSubDivisions() { 
     return subDivisions; 
    } 
... 
} 

Da ich um hierarchische Struktur und JPA einig umfangreiche Business-Logik hatte (basierend auf relationales Modell) ist sehr schwach, sie zu unterstützen eingeführt I-Schnittstelle IHierarchyElement und Entity Zuhörer HierarchyListener:

public interface IHierarchyElement { 

    public String getNodeId(); 

    public IHierarchyElement getParent(); 

    public Short getLevel(); 

    public void setLevel(Short level); 

    public IHierarchyElement getTop(); 

    public void setTop(IHierarchyElement top); 

    public String getTreePath(); 

    public void setTreePath(String theTreePath); 
} 


public class HierarchyListener { 

    @PrePersist 
    @PreUpdate 
    public void setHierarchyAttributes(IHierarchyElement entity) { 
     final IHierarchyElement parent = entity.getParent(); 

     // set level 
     if (parent == null) { 
      entity.setLevel((short) 0); 
     } else { 
      if (parent.getLevel() == null) { 
       throw new PersistenceException("Parent entity must have level defined"); 
      } 
      if (parent.getLevel() == Short.MAX_VALUE) { 
       throw new PersistenceException("Maximum number of hierarchy levels reached - please restrict use of parent/level relationship for " 
         + entity.getClass()); 
      } 
      entity.setLevel(Short.valueOf((short) (parent.getLevel().intValue() + 1))); 
     } 

     // set top 
     if (parent == null) { 
      entity.setTop(entity); 
     } else { 
      if (parent.getTop() == null) { 
       throw new PersistenceException("Parent entity must have top defined"); 
      } 
      entity.setTop(parent.getTop()); 
     } 

     // set tree path 
     try { 
      if (parent != null) { 
       String parentTreePath = StringUtils.isNotBlank(parent.getTreePath()) ? parent.getTreePath() : ""; 
       entity.setTreePath(parentTreePath + parent.getNodeId() + "."); 
      } else { 
       entity.setTreePath(null); 
      } 
     } catch (UnsupportedOperationException uoe) { 
      LOGGER.warn(uoe); 
     } 
    } 

} 
+1

Warum nicht die einfachere @OneToMany (mappedBy = "DIV_PARENT_ID") anstelle von @ManyToMany (...) mit selbstverweisenden Attributen verwenden? Das erneute Speichern von Tabellen- und Spaltennamen verstößt gegen DRY. Vielleicht gibt es einen Grund dafür, aber ich sehe es nicht. Außerdem ist das EntityListener-Beispiel ordentlich, aber nicht portabel, wenn man annimmt, dass "Top" eine Beziehung ist. Seite 93 der JPA 2.0-Spezifikation, Entity Listeners und Callback-Methoden: "Im Allgemeinen sollte die Lebenszyklusmethode einer portablen Anwendung keine EntityManager- oder Query-Operationen aufrufen, auf andere Entitätsinstanzen zugreifen oder Beziehungen ändern". Recht? Lass es mich wissen, wenn ich weg bin. –

+0

Meine Lösung ist 3 Jahre alt mit JPA 1.0. Ich habe es unverändert vom Produktionscode übernommen. Ich bin mir sicher, dass ich einige Spaltennamen herausnehmen könnte, aber das war nicht der Punkt. Ihre Antwort macht es genau und einfacher, nicht sicher, warum ich damals viele-zu-viele verwendet habe - aber es funktioniert und ich bin zuversichtlich, dass eine komplexere Lösung aus einem bestimmten Grund da war. Allerdings muss ich das jetzt noch einmal besprechen. – topchef

+0

Ja, oben ist eine Selbstreferenz, daher eine Beziehung. Genau genommen ändere ich es nicht - initialisiere es einfach. Außerdem ist es unidirektional, also gibt es keine Abhängigkeit umgekehrt, es verweist nicht auf andere Entitäten als auf das Selbst. Pro Ihre Angebotsspezifikation hat "im Allgemeinen" was bedeutet, dass es keine strenge Definition ist. Ich glaube, dass in diesem Fall das Portabilitätsrisiko sehr gering ist. – topchef