Lazy-Load-Anwendungsdesign im Ruhezustand

87

Ich neige dazu, Hibernate in Kombination mit dem Spring- Framework und seinen deklarativen Transaktionsabgrenzungsfunktionen (z . B. @Transactional ) zu verwenden.

Wie wir alle wissen, versucht der Winterschlaf, so wenig invasiv und transparent wie möglich zu sein. Dies ist jedoch bei der Verwendung von Beziehungen etwas schwierigerlazy-loaded .


Ich sehe eine Reihe von Designalternativen mit unterschiedlichen Transparenzstufen.

  1. Machen Sie Beziehungen nicht faul geladen (z. fetchType=FetchType.EAGER)
    • Dies vioalites die gesamte Idee des faulen Ladens ..
  2. Initialisieren Sie Sammlungen mit Hibernate.initialize(proxyObj);
    • Dies impliziert eine relativ hohe Kopplung an das DAO
    • Obwohl wir eine Schnittstelle mit definieren können initialize, wird nicht garantiert, dass andere Implementierungen eine Entsprechung bieten.
  3. Fügen Sie den persistenten ModelObjekten selbst Transaktionsverhalten hinzu (entweder mit dynamischem Proxy oder @Transactional)
    • Ich habe den dynamischen Proxy-Ansatz nicht ausprobiert, obwohl ich @Transactional anscheinend nie dazu gebracht habe, an den persistenten Objekten selbst zu arbeiten. Wahrscheinlich aufgrund dieses Ruhezustands ist die Operation auf einem Proxy zu sein.
    • Kontrollverlust, wenn Transaktionen tatsächlich stattfinden
  4. Stellen Sie sowohl die Lazy / Non-Lazy-API bereit, z. B. loadData()undloadDataWithDeps()
    • Erzwingt, dass die Anwendung weiß, wann welche Routine anzuwenden ist, wieder eine enge Kopplung
    • Methodenüberlauf ,, loadDataWithA()....,loadDataWithX()
  5. Erzwingen Sie die Suche nach Abhängigkeiten, z. B. indem Sie nur byId()Operationen bereitstellen
    • Benötigt eine Menge nicht objektorientierter Routinen, z. B. findZzzById(zid)und dann getYyyIds(zid)anstelle vonz.getY()
    • Es kann nützlich sein, jedes Objekt in einer Sammlung einzeln abzurufen, wenn zwischen den Transaktionen ein großer Verarbeitungsaufwand besteht.
  6. Machen Sie einen Teil der Anwendung @Transactional anstelle nur des DAO
    • Mögliche Überlegungen zu verschachtelten Transaktionen
    • Erfordert Routinen, die für das Transaktionsmanagement angepasst sind (z. B. ausreichend klein)
    • Kleine programmatische Auswirkungen, obwohl dies zu großen Transaktionen führen kann
  7. Stellen Sie dem DAO dynamische Abrufprofile zur Verfügung , z.loadData(id, fetchProfile);
    • Anwendungen müssen wissen, welches Profil wann verwendet werden soll
  8. Transaktionen vom Typ AoP, z. B. Abfangen von Operationen und Ausführen von Transaktionen, falls erforderlich
    • Erfordert die Manipulation von Bytecode oder die Verwendung von Proxys
    • Kontrollverlust bei Transaktionen
    • Schwarze Magie wie immer :)

Habe ich eine Option verpasst?


Welches ist Ihr bevorzugter Ansatz, wenn Sie versuchen, die Auswirkungen von lazy-loadedBeziehungen in Ihrem Anwendungsdesign zu minimieren ?

(Oh, und Entschuldigung für WoT )

Johan Sjöberg
quelle
Beispiel für Option 2 & 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be
Könnten Sie bitte ein Beispiel für Option 4 angeben?
Gradightdc

Antworten:

26

Wie wir alle wissen, versucht der Winterschlaf, so wenig invasiv und transparent wie möglich zu sein

Ich würde sagen, die ursprüngliche Annahme ist falsch. Transaparente Persistenz ist ein Mythos, da die Anwendung immer den Entitätslebenszyklus und die Größe des geladenen Objektgraphen berücksichtigen sollte.

Beachten Sie, dass der Ruhezustand keine Gedanken lesen kann. Wenn Sie also wissen, dass Sie bestimmte Abhängigkeiten für einen bestimmten Vorgang benötigen, müssen Sie Ihre Absichten für den Ruhezustand irgendwie zum Ausdruck bringen.

Unter diesem Gesichtspunkt sehen Lösungen, die diese Absichten explizit ausdrücken (nämlich 2, 4 und 7), vernünftig aus und leiden nicht unter dem Mangel an Transparenz.

axtavt
quelle
Sie haben natürlich Recht, das so transparent wie möglich funktioniert bisher nur. Das sind einige gute Entscheidungen, die Sie getroffen haben.
Johan Sjöberg
IMHO: vollkommen richtige Antwort. In der Tat ist es ein Mythos. Übrigens: Ich würde für die Optionen 4 und 7 stimmen (oder mich überhaupt vom ORM
entfernen
7

Ich bin nicht sicher, auf welches Problem (verursacht durch Faulheit) Sie hinweisen, aber für mich besteht der größte Schmerz darin, den Sitzungskontext in meinen eigenen Anwendungscaches nicht zu verlieren. Typischer Fall:

  • Objekt foowird geladen und in eine Karte eingefügt;
  • Ein anderer Thread nimmt dieses Objekt von der Karte und ruft es auf foo.getBar()(etwas, das noch nie aufgerufen wurde und faul ausgewertet wird).
  • Boom!

Um dies anzugehen, haben wir eine Reihe von Regeln:

  • Wrap-Sitzungen so transparent wie möglich (z. B. OpenSessionInViewFilterfür Webapps);
  • haben eine gemeinsame API für Threads / Thread-Pools, bei denen das Binden / Aufheben der Bindung von DB-Sitzungen an einer hohen Stelle in der Hierarchie erfolgt (eingeschlossen try/finally), sodass Unterklassen nicht darüber nachdenken müssen;
  • Übergeben Sie beim Übergeben von Objekten zwischen Threads IDs anstelle von Objekten selbst. Der empfangende Thread kann bei Bedarf ein Objekt laden.
  • Zwischenspeichern Sie beim Zwischenspeichern von Objekten niemals Objekte, sondern deren IDs. Verfügen Sie über eine abstrakte Methode in Ihrer DAO- oder Manager-Klasse, um das Objekt aus dem Ruhezustand-Cache der zweiten Ebene zu laden, wenn Sie die ID kennen. Die Kosten für das Abrufen von Objekten aus dem Ruhezustand-Cache der zweiten Ebene sind immer noch weitaus günstiger als die Kosten für die DB.

Wie Sie sehen, ist dies in der Tat nicht annähernd nicht invasiv und transparent . Aber die Kosten sind immer noch erträglich, verglichen mit dem Preis, den ich für eifriges Laden zahlen müsste. Das Problem bei letzterem ist, dass es manchmal zum Schmetterlingseffekt führt, wenn ein einzelnes referenziertes Objekt geladen wird, geschweige denn eine Sammlung von Entitäten. Der Speicherverbrauch, die CPU-Auslastung und die Latenz, um es gelinde auszudrücken, sind ebenfalls weitaus schlechter, sodass ich damit leben kann.

mindas
quelle
Danke für deine Antwort. Der Verlust transparencybesteht darin, dass die Anwendung gezwungen wird, sich um das Laden fauler Objekte zu kümmern. Wenn alles geholt wurde eifrig die Anwendung konnte vollständig nicht bewusst sein , ob die Objekte in einer Datenbank beibehalten werden oder nicht, da Foo.getBar()immer erfolgreich. > when passing objects between threads, pass IDsja, das würde # 5 entsprechen.
Johan Sjöberg
3

Ein sehr häufiges Muster ist die Verwendung von OpenEntityManagerInViewFilter, wenn Sie eine Webanwendung erstellen.

Wenn Sie einen Dienst erstellen, würde ich den TX für die öffentliche Methode des Dienstes und nicht für die DAOs öffnen, da eine Methode sehr oft das Abrufen oder Aktualisieren mehrerer Entitäten erfordert.

Dadurch wird jede "Lazy Load-Ausnahme" gelöst. Wenn Sie für die Leistungsoptimierung etwas Fortgeschritteneres benötigen, ist das Abrufen von Profilen meiner Meinung nach der richtige Weg.

Augusto
quelle
1
Ich denke , man sagen wollte: Eine sehr häufige Antipattern ... . Ich würde zwar dem Öffnen von TX auf Serviceebene zustimmen, aber die Verwendung von OSIVist immer noch ein Antimuster und führt zu sehr schwerwiegenden Problemen wie der Unfähigkeit, Ausnahmen oder Leistungseinbußen ordnungsgemäß zu behandeln. Zusammenfassend: IMHO OSIV ist eine unkomplizierte Lösung, aber nur für Spielzeugprojekte geeignet.
G. Demecki