Antworten:
Ja, das ist möglich. Dies ist ein Spezialfall der bidirektionalen Standard @ManyToOne/ @OneToManyBeziehung. Es ist etwas Besonderes, weil die Entität an jedem Ende der Beziehung dieselbe ist. Der allgemeine Fall ist in Abschnitt 2.10.2 der JPA 2.0-Spezifikation beschrieben .
Hier ist ein Beispiel. Erstens 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 ist eine grobe main()Methode, die drei solcher Entitäten beibehält:
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 müssen alle drei Entitätsinstanzen vor dem Festschreiben der Transaktion beibehalten werden. Wenn ich eine der Entitäten im Diagramm der Eltern-Kind-Beziehungen nicht beibehalten kann, wird eine Ausnahme ausgelöst commit(). Bei Eclipselink wird RollbackExceptiondie Inkonsistenz detailliert beschrieben.
Dieses Verhalten kann über das cascadeAttribut Aons @OneToManyund @ManyToOneAnmerkungen konfiguriert werden . Wenn ich beispielsweise cascade=CascadeType.ALLbeide Anmerkungen festlege , kann ich eine der Entitäten sicher beibehalten und die anderen ignorieren. Angenommen, ich habe parentmeine Transaktion fortgesetzt . Die JPA-Implementierung durchläuft parentdie childrenEigenschaft, da sie mit gekennzeichnet ist CascadeType.ALL. Die JPA-Implementierung findet sonund daughterdort. Es bleiben dann beide Kinder in meinem Namen bestehen, obwohl ich es nicht ausdrücklich angefordert habe.
Noch eine Anmerkung. Es liegt immer 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 Eltern-Eigenschaft des Kindes entsprechend aktualisieren. Das Aktualisieren nur einer Seite einer bidirektionalen Beziehung ist unter JPA ein Fehler. Aktualisieren Sie immer beide Seiten der Beziehung. Dies ist eindeutig auf Seite 42 der JPA 2.0-Spezifikation geschrieben:
Beachten Sie, dass die Anwendung für die Aufrechterhaltung der Konsistenz von Laufzeitbeziehungen verantwortlich ist, z. B. um sicherzustellen, dass die "eine" und die "viele" Seite einer bidirektionalen Beziehung miteinander konsistent sind, wenn die Anwendung die Beziehung zur Laufzeit aktualisiert .
Für mich bestand der Trick darin, eine Viele-zu-Viele-Beziehung zu verwenden. Angenommen, Ihre Entität A ist eine Abteilung, die Unterteilungen haben kann. Dann (Überspringen irrelevanter Details):
@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 eine umfangreiche Geschäftslogik in Bezug auf die hierarchische Struktur hatte und JPA (basierend auf dem relationalen Modell) sehr schwach ist, um dies zu unterstützen, habe ich den IHierarchyElementListener für Schnittstellen und Entitäten eingeführt 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);
}
}
}
Tophandelt sich um eine Beziehung. Seite 93 der JPA 2.0-Spezifikation, Entitätslistener und Rückrufmethoden: "Im Allgemeinen sollte die Lebenszyklusmethode einer tragbaren Anwendung keine EntityManager- oder Abfragevorgänge aufrufen, nicht auf andere Entitätsinstanzen zugreifen oder Beziehungen ändern." Richtig? Lass es mich wissen, wenn ich weg bin.