Letzte Woche haben wir unsere Suche nach JPA-Implementierungsmustern mit dem Data Access Object-Muster begonnen. Diese Woche machen wir mit einem anderen haarigen Thema weiter. JPA bietet die Annotationen @OneToMany, @ManyToOne, @OneToOne und @ManyToMany, um Assoziationen zwischen Objekten abzubilden. Während EJB 2.x containerverwaltete Beziehungen zur Verwaltung dieser Assoziationen und insbesondere zur Synchronisierung bidirektionaler Assoziationen anbot, überlässt JPA mehr dem Entwickler.
Die Einrichtung
Beginnen wir mit der Erweiterung des Order-Beispiels aus dem vorherigen Blog um ein OrderLine-Objekt. Es hat eine ID, eine Beschreibung, einen Preis und einen Verweis auf die Bestellung, die es enthält:
@Entität
public class OrderLine {
@Id
@GeneratedValue
private int id;
private String description;
private int Preis;
@ManyToOne
private Order bestellen;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getPreis() { return preis; }
public void setPrice(int price) { this.price = price; }
public Order getOrder() { return order; }
public void setOrder(Order order) { this.order = order; }
}
Mithilfe des generischen DAO-Musters erhalten wir schnell eine sehr einfache OrderLineDao-Schnittstelle und eine Implementierung:
public interface OrderLineDao extends Dao {
public List findOrderLinesByOrder(Order o);
}
public class JpaOrderLineDao extends JpaDao implements
OrderLineDao {
public Liste findOrderLinesByOrder(Order o) {
Abfrage q = entityManager.createQuery("SELECT e FROM "
+ entityClass.getName() + " e WHERE order = :entity");
q.setParameter("entity", o);
return (Liste) q.getResultList();
}
}
Wir können diese DAO verwenden, um eine Bestellzeile zu einer Bestellung hinzuzufügen oder um alle Bestellzeilen für eine Bestellung zu finden:
OrderLine line = new OrderLine();
line.setDescription("Java Persistence mit Hibernate");
line.setPrice(5999);
line.setOrder(o);
orderLineDao.persist(line);
Collection lines = orderLineDao.findOrderLinesByOrder(o);
Mehr Vereine, mehr Probleme
All dies ist ziemlich einfach, aber interessant wird es, wenn wir diese Assoziation bidirektional machen. Fügen wir unserem Order-Objekt ein orderLines-Feld hinzu und fügen eine naive Implementierung des Getter/Setter-Paares hinzu:
@OneToMany(mappedBy = "Bestellung")
private Set orderLines = new HashSet();
public Set getOrderLines() { return orderLines; }
public void setOrderLines(Set orderLines) { this.orderLines = orderLines; }
Collection lines = o.getOrderLines();
Sie müssen nicht mehr auf den OrderLineDao zugreifen :-)
Bestellung o = new Bestellung();
o.setCustomerName("Mary Jackson");
o.setDate(new Datum());
OrderLine line = new OrderLine();
line.setDescription("Java Persistence mit Hibernate");
line.setPrice(5999);
line.setOrder(o);
System.out.println("Artikel bestellt von " + o.getCustomerName() + ": ");
Collection lines = o.getOrderLines();
for (OrderLine each : lines) {
System.out.println(jede.getId() + ": " + jede.getDescription()
+ " zu $" + each.getPrice());
}
Dies kann behoben werden, indem Sie die folgende Zeile vor der ersten System.out.println-Anweisung hinzufügen:
o.getOrderLines().add(line);
Reparieren und reparieren...
Es funktioniert, aber es ist nicht sehr schön. Es bricht die Abstraktion auf und ist brüchig, da es vom Benutzer unserer Domänenobjekte abhängt, diese Setter und Addierer korrekt aufzurufen. Wir können dies beheben, indem wir diesen Aufruf in die Definition von OrderLine.setOrder(Order) verschieben:
public void setOrder(Order order) {
this.order = order;
order.getOrderLines().add(this);
}
Wenn Sie die Eigenschaft orderLines des Objekts Order noch besser kapseln können:
public Set getOrderLines() { return orderLines; }
public void addOrderLine(OrderLine line) { orderLines.add(line); }
Und dann können wir OrderLine.setOrder(Order) wie folgt umdefinieren:
public void setOrder(Order order) {
this.order = order;
order.addOrderLine(this);
}
Sind Sie immer noch bei mir? Ich hoffe es, aber wenn nicht, probieren Sie es bitte aus und überzeugen Sie sich selbst.
public void addOrderLine(OrderLine line) {
orderLines.add(line);
line.setOrder(this);
}
Dieses Problem lässt sich lösen, indem Sie eine Methode Order.internalAddOrderLine(OrderLine) einführen, die die Zeile nur zur Sammlung hinzufügt, aber line.setOrder(this) nicht aufruft. Diese Methode wird dann von OrderLine.setOrder(Order) aufgerufen und führt nicht zu einer Endlosschleife. Benutzer der Klasse Order sollten Order.addOrderLine(OrderLine) aufrufen.
Das Muster
Wenn wir diese Idee zu Ende denken, kommen wir zu diesen Methoden für die Klasse OrderLine:
public Order getOrder() { return order; }
public void setOrder(Order order) {
if (this.order != null) { this.order.internalRemoveOrderLine(this); }
this.order = order;
if (order != null) { order.internalAddOrderLine(this); }
}
Und diese Methoden für die Klasse Order:
public Set getOrderLines() { return Collections.unmodifiableSet(orderLines); }
public void addOrderLine(OrderLine line) { line.setOrder(this); }
public void removeOrderLine(OrderLine line) { line.setOrder(null); }
public void internalAddOrderLine(OrderLine line) { orderLines.add(line); }
public void internalRemoveOrderLine(OrderLine line) { orderLines.remove(line); }
Diese Methoden bieten eine POJO-basierte Implementierung der CMR-Logik, die in EJB 2.x eingebaut wurde. Mit den typischen POJO-Vorteilen, dass sie leichter zu verstehen, zu testen und zu warten sind. Natürlich gibt es eine Reihe von Variationen zu diesem Thema:
- Wenn sich Order und OrderLine im selben Paket befinden, können Sie den internen... Methoden Paketumfang geben, um zu verhindern, dass sie versehentlich aufgerufen werden. (An dieser Stelle wäre das Konzept der Freundesklassen von C++ sehr nützlich. Aber lassen wir das lieber. ;-) ).
- Sie können auf die Methoden removeOrderLine und internalRemoveOrderLine verzichten, wenn die Bestellzeilen nie aus einer Bestellung entfernt werden sollen.
- Sie können die Verantwortung für die Verwaltung der bidirektionalen Assoziation von der Methode OrderLine.setOrder(Order) in die Klasse Order verlagern und damit die Idee im Grunde umdrehen. Aber das würde bedeuten, dass Sie die Logik auf die Methoden addOrderLine und removeOrderLine verteilen müssten.
- Anstatt oder zusätzlich zur Verwendung von Collections.singletonSet, um den OrderLine-Satz zur Laufzeit schreibgeschützt zu machen, können Sie auch generische Typen verwenden, um ihn zur Kompilierungszeit schreibgeschützt zu machen:
public Set getOrderLines() { return Collections.unmodifiableSet(orderLines); }Dies macht es jedoch schwieriger, diese Objekte mit einem Mocking-Framework wie EasyMock zu spotten.
Es gibt auch einige Dinge zu beachten, wenn Sie dieses Muster verwenden:
- Wenn Sie eine OrderLine zu einer Order hinzufügen, wird sie nicht automatisch persistiert. Dazu müssen Sie auch die persist-Methode für die DAO (oder den EntityManager) aufrufen. Oder Sie können die
Kaskadeneigenschaft der@OneToMany-Annotation der EigenschaftOrder.orderLines auf setzen (mindestens), um dies zu erreichen. Mehr dazu erfahren Sie, wenn wir die EntityManager.persist Methode besprechen.KaskadenTyp.PERSIST - Bidirektionale Assoziationen funktionieren nicht gut mit der Funktion EntityManager.merge Methode. Wir werden dies besprechen, wenn wir zum Thema abgetrennte Objekte kommen.
- Wenn eine Entität, die Teil einer bidirektionalen Assoziation ist, (demnächst) entfernt wird, sollte sie auch vom anderen Ende der Assoziation entfernt werden. Dies wird auch zur Sprache kommen, wenn wir über die Funktion EntityManager.remove Methode sprechen.
- Das obige Muster funktioniert nur, wenn Sie den Feldzugriff (anstelle des Zugriffs auf Eigenschaften/Methoden) verwenden, damit Ihr JPA-Anbieter Ihre Entitäten auffüllen kann. Feldzugriff wird verwendet, wenn die @Id Annotation Ihrer Entität auf das entsprechende Feld und nicht auf den entsprechenden Getter gesetzt wird. Ob Sie den Feldzugriff oder den Eigenschafts-/Methodenzugriff bevorzugen, ist eine umstrittene Frage, auf die ich in einem späteren Blog zurückkommen werde.
- Und zu guter Letzt: Auch wenn es sich bei diesem Muster um eine technisch solide POJO-basierte Implementierung von verwalteten Assoziationen handelt, können Sie darüber streiten, warum Sie all diese Getter und Setter benötigen. Warum sollten Sie in der Lage sein, sowohl Order.addOrderLine(OrderLine) als auch OrderLine.setOrder(Order) zu verwenden, um das gleiche Ergebnis zu erzielen? Der Verzicht auf eines dieser Elemente könnte unseren Code einfacher machen. Siehe zum Beispiel den Artikel von James Holub über Getter und Setter. Andererseits haben wir festgestellt, dass dieses Muster Entwicklern, die diese Domänenobjekte verwenden, die Flexibilität gibt, sie nach Belieben zuzuordnen.
Nun, das war's für heute. Ich bin sehr gespannt auf Ihr Feedback zu diesem Thema und darauf, wie Sie bidirektionale Assoziationen in Ihren JPA-Domänenobjekten verwalten. Und natürlich: Wir sehen uns beim nächsten Pattern! Eine Liste aller Blogs zu JPA-Implementierungsmustern finden Sie in der Zusammenfassung der JPA-Implementierungsmuster.
Verfasst von
Vincent Partington
Unsere Ideen
Weitere Blogs
Contact



