Warum wird Hibernate Open Session in View als schlechte Praxis angesehen?

106

Und welche alternativen Strategien verwenden Sie, um LazyLoadExceptions zu vermeiden?

Ich verstehe, dass die offene offene Sitzung Probleme hat mit:

  • Überlagerte Anwendungen, die in verschiedenen JVMs ausgeführt werden
  • Transaktionen werden erst am Ende festgeschrieben, und höchstwahrscheinlich möchten Sie die Ergebnisse vorher.

Wenn Sie jedoch wissen, dass Ihre Anwendung auf einer einzelnen VM ausgeführt wird, können Sie Ihre Schmerzen durch die Verwendung einer offenen Sitzungsstrategie lindern.

HeDinges
quelle
12
Wird OSIV als schlechte Praxis angesehen? Von wem?
Johannes Brodwall
4
Und - was sind gute Alternativen?
David Rabinowitz
7
Diese Textfreiheit von Nahtentwicklern: Es gibt mehrere Probleme mit dieser Implementierung. Das schwerwiegendste ist, dass wir niemals sicher sein können, dass eine Transaktion erfolgreich ist, bis wir sie festschreiben, aber bis die Transaktion "Sitzung in Sicht öffnen" festgeschrieben wird. Die Ansicht wird vollständig gerendert, und die gerenderte Antwort wurde möglicherweise bereits auf den Client übertragen. Wie können wir den Benutzer benachrichtigen, dass seine Transaktion nicht erfolgreich war?
Darpet
2
In diesem Blog-Beitrag finden Sie Vor- und Nachteile sowie meine eigenen Erfahrungen dazu - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Antworten:

46

Da das Senden möglicherweise nicht initialisierter Proxies, insbesondere von Sammlungen, in der Ansichtsebene und das Auslösen des Ladens im Ruhezustand von dort aus sowohl aus Sicht der Leistung als auch des Verständnisses problematisch sein kann.

Verständnis :

Durch die Verwendung von OSIV wird die Ansichtsebene mit Bedenken in Bezug auf die Datenzugriffsschicht "verschmutzt".

Die Ansichtsebene ist nicht darauf vorbereitet, eine zu behandeln, HibernateExceptiondie beim verzögerten Laden auftreten kann, vermutlich jedoch die Datenzugriffsebene.

Leistung :

OSIV neigt dazu, das richtige Laden von Entitäten unter den Teppich zu ziehen - Sie bemerken nicht, dass Ihre Sammlungen oder Entitäten träge initialisiert sind (möglicherweise N + 1). Mehr Komfort, weniger Kontrolle.


Update: Weitere Informationen zu diesem Thema finden Sie unter Das OpenSessionInView-Antimuster . Der Autor listet drei wichtige Punkte auf:

  1. Bei jeder verzögerten Initialisierung erhalten Sie eine Abfrage, dh jede Entität benötigt N + 1 Abfragen, wobei N die Anzahl der verzögerten Zuordnungen ist. Wenn auf Ihrem Bildschirm tabellarische Daten angezeigt werden, ist das Lesen des Hibernate-Protokolls ein wichtiger Hinweis darauf, dass Sie nicht das tun, was Sie sollten
  2. Dadurch wird die geschichtete Architektur vollständig zunichte gemacht, da Sie Ihre Nägel mit DB in der Präsentationsebene beschmutzen. Dies ist ein konzeptioneller Nachteil, also könnte ich damit leben, aber es gibt eine Konsequenz
  3. Wenn beim Abrufen der Sitzung eine Ausnahme auftritt, tritt diese beim Schreiben der Seite auf: Sie können dem Benutzer keine saubere Fehlerseite präsentieren, und Sie können nur eine Fehlermeldung in den Text schreiben
Robert Munteanu
quelle
13
Ok, es verschmutzt die Ansichtsebene mit Ausnahme des Ruhezustands. Aber in Bezug auf die Leistung denke ich, dass das Problem ziemlich ähnlich ist, als auf eine Service-Schicht zuzugreifen, die Ihr dto zurückgibt. Wenn Sie auf ein Leistungsproblem stoßen, sollten Sie dieses spezielle Problem mit einer intelligenteren Abfrage oder einem leichteren dto optimieren. Wenn Sie zu viele Servicemethoden entwickeln müssen, um die Möglichkeiten zu nutzen, die Sie in der Ansicht benötigen, verschmutzen Sie auch die Serviceschicht. Nein?
HeDinges
1
Ein Unterschied besteht darin, dass das Schließen der Hibernate-Sitzung verzögert wird. Sie werden warten, bis die JSP gerendert / geschrieben / etc ist, und dadurch bleiben die Objekte länger im Speicher. Dies kann insbesondere dann ein Problem sein, wenn Sie Daten zum Sitzungs-Commit schreiben müssen.
Robert Munteanu
8
Es macht keinen Sinn zu sagen, dass OSIV die Leistung beeinträchtigt. Welche Alternativen gibt es außer der Verwendung von DTOs? In diesem Fall ist die Leistung immer geringer, da die von einer Ansicht verwendeten Daten auch für Ansichten geladen werden müssen, die sie nicht benötigen.
Johannes Brodwall
11
Ich denke, die Verschmutzung funktioniert umgekehrt. Wenn ich die Daten eifrig laden möchte, muss die Logikschicht (oder schlimmer noch die Datenzugriffsschicht) wissen, auf welche Weise ein Objekt angezeigt werden soll. Wenn Sie die Ansicht ändern, werden Dinge geladen, die Sie nicht benötigen, oder es fehlen Objekte, die Sie benötigen. Eine Ausnahme im Ruhezustand ist ein Fehler und genauso vergiftend wie jede andere unerwartete Ausnahme. Aber Leistung ist ein Problem. Leistungs- und Skalierbarkeitsprobleme zwingen Sie dazu, mehr Gedanken und Arbeit in Ihre Datenzugriffsschicht zu stecken und möglicherweise die Sitzung früher zu schließen
Jens Schauder,
1
@JensSchauder "Ändern Sie die Ansicht und Sie laden am Ende Dinge, die Sie nicht benötigen, oder fehlende Objekte, die Sie benötigen". Das ist genau das. Wenn Sie die Ansicht ändern, ist es viel besser, nicht benötigte Inhalte zu laden (da Sie diese eher abrufen möchten) oder fehlende Objekte zu ermitteln, da die Ausnahme "Lazy Loading" auftritt, als die Ansicht laden zu lassen es träge, da dies zu dem N + 1-Problem führen wird und Sie nicht einmal wissen, dass es passiert. IMO ist es also besser, wenn die Service-Schicht (und Sie) wissen, was gesendet wird, als wenn die Ansicht träge geladen wird und Sie nichts darüber wissen.
Jeshurun
40

Eine längere Beschreibung finden Sie in meinem Artikel " Open Session In View Anti-Pattern ". Andernfalls finden Sie hier eine Zusammenfassung, warum Sie Open Session In View nicht verwenden sollten.

Open Session In View verfolgt einen schlechten Ansatz beim Abrufen von Daten. Anstatt die Geschäftsschicht entscheiden zu lassen, wie alle Zuordnungen, die von der Ansichtsebene benötigt werden, am besten abgerufen werden, wird der Persistenzkontext geöffnet, damit die Ansichtsebene die Proxy-Initialisierung auslösen kann.

Geben Sie hier die Bildbeschreibung ein

  • Der OpenSessionInViewFilterruft die openSessionMethode des Basiswerts auf SessionFactoryund erhält eine neue Session.
  • Das Sessionist an das gebunden TransactionSynchronizationManager.
  • Das OpenSessionInViewFilterruft die doFilterdie javax.servlet.FilterChainObjektreferenz und die Anforderung wird weiter verarbeitet
  • Das DispatcherServletwird aufgerufen und leitet die HTTP-Anforderung an den Basiswert weiter PostController.
  • Das PostControllerruft das PostServiceauf, um eine Liste der PostEntitäten zu erhalten.
  • Das PostServiceöffnet eine neue Transaktion und das HibernateTransactionManagerwiederverwendet das gleiche Session, das von der geöffnet wurde OpenSessionInViewFilter.
  • Der PostDAOruft die Liste der PostEntitäten ab, ohne eine verzögerte Zuordnung zu initialisieren.
  • Das PostServiceschreibt die zugrunde liegende Transaktion fest, wird jedoch Sessionnicht geschlossen, da sie extern geöffnet wurde.
  • Das DispatcherServletRendering der Benutzeroberfläche beginnt, die wiederum durch die verzögerten Zuordnungen navigiert und deren Initialisierung auslöst.
  • Das OpenSessionInViewFilterkann die schließen Session, und die zugrunde liegende Datenbankverbindung wird ebenfalls freigegeben.

Auf den ersten Blick mag dies nicht schrecklich aussehen, aber sobald Sie es aus einer Datenbankperspektive betrachten, werden eine Reihe von Fehlern offensichtlicher.

Die Serviceschicht öffnet und schließt eine Datenbanktransaktion, danach findet jedoch keine explizite Transaktion statt. Aus diesem Grund wird jede zusätzliche Anweisung aus der UI-Rendering-Phase im Auto-Commit-Modus ausgeführt. Das automatische Festschreiben übt Druck auf den Datenbankserver aus, da jede Anweisung das Transaktionsprotokoll auf die Festplatte leeren muss, wodurch auf der Datenbankseite viel E / A-Verkehr verursacht wird. Eine Optimierung wäre, das Connectionals schreibgeschützt zu markieren, wodurch der Datenbankserver das Schreiben in das Transaktionsprotokoll vermeiden kann.

Es gibt keine Trennung von Bedenken mehr, da Anweisungen sowohl von der Service-Schicht als auch vom UI-Rendering-Prozess generiert werden. Das Schreiben von Integrationstests, die die Anzahl der generierten Anweisungen bestätigen, erfordert das Durchlaufen aller Ebenen (Web, Service, DAO), während die Anwendung auf einem Webcontainer bereitgestellt wird. Selbst wenn eine In-Memory-Datenbank (z. B. HSQLDB) und ein leichtgewichtiger Webserver (z. B. Jetty) verwendet werden, werden diese Integrationstests langsamer ausgeführt als wenn Schichten getrennt würden und die Back-End-Integrationstests die Datenbank verwenden würden, während die Front-End-Integrationstests verspotteten die Service-Schicht insgesamt.

Die UI-Ebene ist auf das Navigieren in Assoziationen beschränkt, was wiederum N + 1-Abfrageprobleme auslösen kann. Obwohl Hibernate Angebote @BatchSizezum Abrufen von Zuordnungen in Stapeln und FetchMode.SUBSELECTzur Bewältigung dieses Szenarios anbietet , wirken sich die Anmerkungen auf den Standardabrufplan aus, sodass sie auf jeden Geschäftsanwendungsfall angewendet werden. Aus diesem Grund ist eine Datenzugriffsschichtabfrage viel besser geeignet, da sie auf die aktuellen Anforderungen für das Abrufen von Anwendungsfalldaten zugeschnitten werden kann.

Last but not least kann die Datenbankverbindung während der gesamten Rendering-Phase der Benutzeroberfläche (abhängig von Ihrem Verbindungsfreigabemodus) gehalten werden, wodurch die Verbindungslease-Zeit verlängert und der gesamte Transaktionsdurchsatz aufgrund einer Überlastung des Datenbankverbindungspools begrenzt wird. Je länger die Verbindung gehalten wird, desto mehr andere gleichzeitige Anforderungen warten darauf, eine Verbindung aus dem Pool zu erhalten.

Entweder wird die Verbindung zu lange gehalten, oder Sie erwerben / geben mehrere Verbindungen für eine einzelne HTTP-Anforderung frei, wodurch der zugrunde liegende Verbindungspool unter Druck gesetzt und die Skalierbarkeit eingeschränkt wird.

Frühlingsstiefel

Leider ist Open Session in View in Spring Boot standardmäßig aktiviert .

Stellen Sie also sicher, dass Sie in der application.propertiesKonfigurationsdatei den folgenden Eintrag haben:

spring.jpa.open-in-view=false

Dies wird deaktivieren OSIV, so dass Sie den Griff , LazyInitializationExceptionden richtigen Weg .

Vlad Mihalcea
quelle
3
Die Verwendung von Open Session in View mit Auto-Commit ist möglich, jedoch nicht so, wie es von den Hibernate-Entwicklern beabsichtigt war. Obwohl Open Session in View seine Nachteile hat, ist das automatische Festschreiben keine, da Sie es einfach deaktivieren und trotzdem verwenden können.
stefan.m
Sie sprechen darüber, was innerhalb einer Transaktion passiert, und das stimmt. Die Web-Layer-Rendering-Phase findet jedoch außerhalb des Ruhezustands statt, sodass Sie den Autocommit-Modus erhalten. Macht Sinn?
Vlad Mihalcea
Ich denke, das ist eine Variante, die für Open Session in View nicht optimal ist. Die Sitzung und die Transaktion sollten geöffnet bleiben, bis die Ansicht gerendert wurde. Dann ist kein Autocommit-Modus erforderlich.
stefan.m
2
Die Sitzung bleibt offen. Die Transaktion jedoch nicht. Die Verteilung der Transaktion über den gesamten Prozess ist ebenfalls nicht optimal, da sie länger wird und Sperren länger als nötig gehalten werden. Stellen Sie sich vor, was passiert, wenn die Ansicht eine RuntimeException auslöst. Wird die Transaktion zurückgesetzt, weil das Rendern der Benutzeroberfläche fehlgeschlagen ist?
Vlad Mihalcea
Vielen Dank für die sehr ausführliche Antwort! Ich würde die Anleitung am Ende nur ändern, da Spring Boot-Benutzer jpa wahrscheinlich nicht auf diese Weise verwenden werden.
Skeeve
24
  • Transaktionen können in der Service-Schicht festgeschrieben werden - Transaktionen beziehen sich nicht auf OSIV. Es ist das Session, was offen bleibt, keine Transaktion - läuft.

  • Wenn Ihre Anwendungsebenen auf mehrere Computer verteilt sind, können Sie OSIV so gut wie nicht verwenden. Sie müssen alles initialisieren, was Sie benötigen, bevor Sie das Objekt über das Kabel senden.

  • OSIV ist eine nette und transparente Möglichkeit (dh keiner Ihrer Codes weiß, dass dies geschieht), um die Leistungsvorteile des verzögerten Ladens zu nutzen

Bozho
quelle
2
In Bezug auf den ersten Aufzählungspunkt gilt dies zumindest nicht für das ursprüngliche OSIV aus dem JBoss-Wiki, sondern behandelt auch die Transaktionsabgrenzung um die Anforderung.
Pascal Thivent
@PascalThivent Welcher Teil hat dich dazu gebracht, das zu denken?
Sanghyun Lee
13

Ich würde nicht sagen, dass Open Session In View als schlechte Praxis angesehen wird. Was gibt dir diesen Eindruck?

Open-Session-In-View ist ein einfacher Ansatz zur Behandlung von Sitzungen mit Hibernate. Weil es einfach ist, ist es manchmal simpel. Wenn Sie eine differenzierte Kontrolle über Ihre Transaktionen benötigen, z. B. mehrere Transaktionen in einer Anfrage, ist Open-Session-In-View nicht immer ein guter Ansatz.

Wie andere bereits betont haben, gibt es einige Kompromisse bei OSIV - Sie sind viel anfälliger für das N + 1-Problem, da Sie weniger wahrscheinlich erkennen, welche Transaktionen Sie starten. Gleichzeitig bedeutet dies, dass Sie Ihre Service-Schicht nicht ändern müssen, um sich an geringfügige Änderungen in Ihrer Ansicht anzupassen.

Geoffrey Wiseman
quelle
5

Wenn Sie einen IoC-Container (Inversion of Control) wie Spring verwenden, sollten Sie sich über das Bean-Scoping informieren . Im Wesentlichen fordere ich Spring auf, mir ein Hibernate- SessionObjekt zu geben, dessen Lebenszyklus die gesamte Anforderung umfasst (dh es wird zu Beginn und am Ende der HTTP-Anforderung erstellt und zerstört). Ich muss mich weder um LazyLoadExceptions kümmern noch die Sitzung schließen, da der IoC-Container dies für mich verwaltet.

Wie bereits erwähnt, müssen Sie über Leistungsprobleme bei N + 1 SELECT nachdenken. Sie können Ihre Hibernate-Entität danach jederzeit so konfigurieren, dass sie an Stellen, an denen die Leistung ein Problem darstellt, eifrig Join-Ladevorgänge ausführt.

Die Bohnen-Scoping-Lösung ist nicht federspezifisch. Ich weiß, dass PicoContainer die gleiche Funktion bietet, und ich bin sicher, dass andere ausgereifte IoC-Container etwas Ähnliches bieten.

0sumgain
quelle
1
Haben Sie einen Zeiger auf eine tatsächliche Implementierung von Hibernate-Sitzungen, die in der Ansicht über Beans mit Anforderungsbereich verfügbar gemacht werden?
Marvo
4

Nach meiner eigenen Erfahrung ist OSIV nicht so schlecht. Die einzige Vereinbarung, die ich getroffen habe, besteht darin, zwei verschiedene Transaktionen zu verwenden: - die erste, die in der "Serviceschicht" geöffnet ist, wo ich die "Geschäftslogik" habe - die zweite, die unmittelbar vor dem Rendern der Ansicht geöffnet wurde

Davide
quelle
3

Ich habe gerade einen Beitrag über einige Richtlinien geschrieben, wann die offene Sitzung in meinem Blog angezeigt werden soll. Probieren Sie es aus, wenn Sie interessiert sind.

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/

Chris Upton
quelle
1
Als allgemeine SO-Faustregel gilt: Wenn Sie eine Antwort geben, ist es am besten, mehr zu tun, als nur an anderer Stelle zu verlinken. Geben Sie vielleicht ein oder zwei Sätze oder aufgelistete Elemente an, die den Kern angeben. Es ist in Ordnung zu verlinken, aber Sie möchten einen kleinen Mehrwert bieten. Andernfalls möchten Sie möglicherweise nur einen Kommentar abgeben und den Link dort einfügen.
DWright
der Link in dieser Antwort ist lesenswert, es eine gute Anleitung, wann bietet OSIV zu verwenden und nicht
ams
1

Ich bin im Ruhezustand sehr verrostet. Aber ich denke, es ist möglich, mehrere Transaktionen in einer Ruhezustandssitzung durchzuführen. Ihre Transaktionsgrenzen müssen also nicht mit den Start / Stopp-Ereignissen der Sitzung identisch sein.

OSIV, imo, ist in erster Linie nützlich, weil wir vermeiden können, Code zum Starten eines 'Persistenzkontexts' (auch als Sitzung bezeichnet) zu schreiben, wenn die Anforderung einen DB-Zugriff benötigt.

In Ihrer Service-Schicht müssen Sie wahrscheinlich Methoden aufrufen, die unterschiedliche Transaktionsanforderungen haben, z. B. "Erforderlich, Neu Erforderlich usw.". Das einzige, was diese Methoden benötigen, ist, dass jemand (dh der OSIV-Filter) den Persistenzkontext gestartet hat, sodass sie sich nur um Folgendes kümmern müssen: "Hey, gib mir die Ruhezustandssitzung für diesen Thread. Ich muss einige tun DB Zeug ".

rjk2008
quelle
1

Dies wird nicht allzu viel helfen, aber Sie können mein Thema hier überprüfen: * Cache1 OutOfMemory mit OpenSessionInView in den Ruhezustand versetzen

Ich habe einige OutOfMemory-Probleme aufgrund von OpenSessionInView und vielen geladenen Entitäten, weil sie im Ruhezustand Cache Level1 bleiben und nicht durch Müll gesammelt werden (ich lade viele Entitäten mit 500 Elementen pro Seite, aber alle Entitäten bleiben im Cache).

Sebastien Lorber
quelle