Vor zwei Wochen habe ich über die Verwendung der Service Facade und des Data Transfer Object-Musters in der JPA-Anwendungsarchitektur gebloggt. Diese Woche werde ich von der High-Level-Perspektive abweichen und eine interessante Interaktion diskutieren, die ich zwischen der Art und Weise, wie bidirektionale Assoziationen verwaltet werden, und Lazy Loading entdeckt habe. Lassen Sie uns also die Ärmel hochkrempeln und uns in dieser nächsten Folge der JPA-Implementierungsmuster-Serie schmutzig machen ;-) In diesem Blog wird davon ausgegangen, dass Sie mit dem Order/OrderLine-Beispiel vertraut sind, das ich in den ersten beiden Blogs dieser Serie vorgestellt habe. Falls nicht, sollten Sie sich das Beispiel noch einmal ansehen. Betrachten Sie den folgenden Code:
OrderLine orderLineToRemove = orderLineDao.findById(30); orderLineToRemove.setOrder(null);
Der Zweck dieses Codes ist es, die Verknüpfung der OrderLine mit der Order aufzuheben, mit der sie zuvor verknüpft war. Sie können sich vorstellen, dies vor dem Entfernen des OrderLine-Objekts zu tun (obwohl Sie auch die Anmerkung tt>@PreRemove verwenden können, damit dies automatisch geschieht) oder wenn Sie die OrderLine mit einer anderen Order-Entität verknüpfen möchten. Wenn Sie diesen Code ausführen, werden die folgenden Entitäten geladen:
- Die OrderLine mit der ID 30.
- Die mit der OrderLine verbundene Order. Dies geschieht, weil die Methode OrderLine.setOrder die Methode Order.internalRemoveOrderLine aufruft, um die OrderLine aus ihrem übergeordneten Order-Objekt zu entfernen.
- Alle anderen OrderLines, die mit dieser Order verbunden sind! Der Order.orderLines-Satz wird geladen, wenn das OrderLine-Objekt mit der ID 30 aus ihm entfernt wird.
Je nach JPA-Anbieter kann dies zwei bis drei Abfragen erfordern. Hibernate verwendet drei Abfragen; eine für jede der hier genannten Zeilen. OpenJPA benötigt nur zwei Abfragen, da es die
- Machen Sie die Verknüpfung nicht bidirektional, sondern behalten Sie nur den Verweis vom untergeordneten Objekt auf das übergeordnete Objekt bei. In diesem Fall würde das bedeuten, dass Sie die
orderLines , die imOrder-Objekt festgelegt sind, entfernen. Um die OrderLines, die zu einer Order gehören, abzurufen, würden Sie die MethodefindOrderLinesByOrder auf dem OrderLineDao aufrufen. Da dies alle untergeordneten Objekte abrufen würde und wir dieses Problem haben, weil es sehr viele davon gibt, müssen Sie spezifischere Abfragen schreiben, um die Teilmenge der untergeordneten Objekte zu finden, die Sie benötigen. Ein Nachteil dieses Ansatzes ist, dass eine Order nicht auf ihre OrderLineszugreifen kann, ohne eine Service-Schicht zu durchlaufen (ein Problem, das wir in einem späteren Blog behandeln werden) oder sie von einer aufrufenden Methode übergeben zu bekommen. - Verwenden Sie die Hibernate-spezifische @LazyCollection Annotation, um eine Sammlung "extra lazy" zu laden:
@OneToMany(mappedBy = "order", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) @org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.EXTRA) public Set orderLines = new HashSet();
Mit dieser Funktion sollte Hibernate in der Lage sein, sehr große Sammlungen zu verarbeiten. Wenn Sie zum Beispiel die Größe der Menge abfragen, lädt Hibernate nicht alle Elemente der Sammlungen. Stattdessen wird es eine SELECT COUNT(*) FROM ... Abfrage ausführen. Aber noch interessanter: Änderungen an der Sammlung werden in eine Warteschlange gestellt, anstatt direkt angewendet zu werden. Wenn beim Zugriff auf die Sammlung Änderungen anstehen, wird die Sitzung geleert, bevor die Arbeit fortgesetzt wird.Während dies für die size() -Methode gut funktioniert, funktioniert es nicht, wenn Sie versuchen, über die Elemente der Menge zu iterieren (siehe JIRA issueHHH-2087 , das seit zweieinhalb Jahren offen ist). Für das zusätzliche faule Laden der Größe gibt es außerdem mindestens zwei offene Bugs:HHH-1491 undHHH-3319 . All dies führt mich zu der Annahme, dass die zusätzliche Funktion für das faule Laden von Hibernate zwar eine nette Idee ist, aber (noch?) nicht ganz ausgereift. - Inspiriert von dem Hibernate-Mechanismus, Operationen in der Sammlung so lange aufzuschieben, bis sie wirklich ausgeführt werden müssen, habe ich die Klasse Order geändert, um etwas Ähnliches zu tun. Zunächst wurde eine Warteschlange für Operationen als transientes Feld zur Klasse Order hinzugefügt:
private transiente Warteschlange queuedOperations = new LinkedList();
Dann wurden die Methoden internalAddOrderLine und internalRemoveOrderLine so geändert, dass sie die eingestellten orderLines nicht direkt verändern. Stattdessen erzeugen sie eine Instanz der entsprechenden Unterklasse der KlasseQueuedOperation . Diese Instanz wird mit dem OrderLine-Objekt initialisiert, das hinzugefügt oder entfernt werden soll, und dann in die Warteschlange von QueuesOperations gestellt:public void internalAddOrderLine(final OrderLine line) { queuedOperations.offer(new Runnable() { public void run() { orderLines.add(line); } }); } public void internalRemoveOrderLine(final OrderLine line) { queuedOperations.offer(new Runnable() { public void run() { orderLines.remove(line); } }); }Schließlich wurde die Methode getOrderLines so geändert, dass sie alle Vorgänge in der Warteschlange ausführt, bevor sie die Menge zurückgibt:public Set getOrderLines() { executeQueuedOperations(); return Collections.unmodifiableSet(orderLines); } private void executeQueuedOperations() { for (;;) { Runnable op = queuedOperations.poll(); if (op == null) Pause; op.run(); } }Wenn es weitere Methoden gäbe, für die das Set auf dem neuesten Stand sein muss, würden sie die executeQueuedOperations-Methode auf ähnliche Weise aufrufen. Der Nachteil dabei ist, dass Ihre Domänenobjekte mit noch mehr "Link-Management-Code" überladen werden, als wir bereits für die Verwaltung bidirektionaler Assoziationen hatten. Die Ausgliederung dieser Logik in eine separate Klasse ist eine Übung für den Leser ;-)
Dieses Problem tritt natürlich nicht nur auf, wenn Sie bidirektionale Verknüpfungen haben. Es tritt immer dann auf, wenn Sie große Sammlungen bearbeiten, die mit tt>@OneToMany oder tt>@ManyToMany zugeordnet sind. Bidirektionale Assoziationen machen die Ursache nur weniger offensichtlich, weil Sie denken, dass Sie nur eine einzige Entität manipulieren.
Nachtrag d.d. 31. Mai 2009: Sie sollten die oben unter Punkt #3 beschriebene Methode nicht verwenden, wenn Sie Operationen auf die zugeordnete Sammlung kaskadieren. Wenn Sie Änderungen an den Sammlungen aufschieben, weiß Ihr JPA-Provider nichts von den hinzugefügten oder entfernten Elementen und wird Operationen auf die falschen Entitäten kaskadieren. Das bedeutet, dass alle Entitäten, die zu einer Sammlung hinzugefügt werden, für die Sie
Verfasst von
Vincent Partington
Unsere Ideen
Weitere Blogs
Contact



