Perché Hibernate Open Session in View è considerata una cattiva pratica?


108

E che tipo di strategie alternative usi per evitare LazyLoadExceptions?

Capisco che la sessione aperta in vista ha problemi con:

  • Applicazioni a strati in esecuzione in diverse jvm
  • Le transazioni vengono eseguite solo alla fine e molto probabilmente vorresti i risultati prima.

Ma, se sai che la tua applicazione è in esecuzione su una singola macchina virtuale, perché non alleviare il tuo dolore utilizzando una strategia di visualizzazione di sessione aperta?


12
L'OSIV è considerata una cattiva pratica? Da chi?
Johannes Brodwall

4
E quali sono le buone alternative?
David Rabinowitz

7
Questa pace di testo proviene dagli sviluppatori di seam: ci sono diversi problemi con questa implementazione, il più grave è che non possiamo mai essere sicuri che una transazione abbia successo fino a quando non la commettiamo ma nel momento in cui la transazione "open session in view" è impegnata, la visualizzazione è completamente renderizzata e la risposta sottoposta a rendering potrebbe essere già stata inviata al client. Come possiamo avvisare l'utente che la sua transazione non è andata a buon fine?
darpet


2
Vedi questo post del blog per pro e contro e la mia esperienza al riguardo - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Risposte:


46

Perché l'invio di proxy possibilmente non inizializzati, in particolare raccolte, nel livello di visualizzazione e l'attivazione del caricamento di ibernazione da lì può essere problematico sia dal punto di vista delle prestazioni che della comprensione.

Comprensione :

L'uso di OSIV "inquina" il livello di visualizzazione con preoccupazioni relative al livello di accesso ai dati.

Il livello di visualizzazione non è pronto a gestire ciò HibernateExceptionche può accadere durante il caricamento lento, ma presumibilmente lo è il livello di accesso ai dati.

Prestazioni :

OSIV tende a trascinare sotto il tappeto il corretto caricamento delle entità: tendi a non notare che le tue collezioni o entità sono inizializzate pigramente (forse N + 1). Più comodità, meno controllo.


Aggiornamento: vedere The OpenSessionInView antipattern per una discussione più ampia su questo argomento. L'autore elenca tre punti importanti:

  1. ogni inizializzazione pigra ti darà una query, il che significa che ogni entità avrà bisogno di N + 1 query, dove N è il numero di associazioni pigre. Se lo schermo presenta dati tabulari, leggere il registro di Hibernate è un grande suggerimento che non devi fare come dovresti
  2. questo sconfigge completamente l'architettura a strati, poiché sporchi le unghie con DB nel livello di presentazione. Questa è una truffa concettuale, quindi potrei conviverci ma c'è un corollario
  3. ultimo ma non meno importante, se si verifica un'eccezione durante il recupero della sessione, si verificherà durante la scrittura della pagina: non puoi presentare una pagina di errore pulita all'utente e l'unica cosa che puoi fare è scrivere un messaggio di errore nel corpo

13
Ok, "inquina" il livello di visualizzazione con l'eccezione di ibernazione. Ma, per quanto riguarda le prestazioni, penso che il problema sia abbastanza simile all'accesso a un livello di servizio che restituirà il tuo dto. Se si verifica un problema di prestazioni, è necessario ottimizzare quel problema specifico con una query più intelligente o un dto più leggero. Se devi sviluppare troppi metodi di servizio per gestire le possibilità di cui potresti aver bisogno nella vista, stai anche "inquinando" il livello di servizio. no?
HeDinges

1
Una differenza è che ritarda la chiusura della sessione di Hibernate. Aspetterete che il JSP venga reso / scritto / ecc., E questo mantiene gli oggetti in memoria più a lungo. Potrebbe essere un problema soprattutto se è necessario scrivere dati sul commit della sessione.
Robert Munteanu,

8
Non ha senso dire che OSIV danneggia le prestazioni. Quali alternative ci sono ad eccezione dell'uso dei DTO? In tal caso, avrai sempre prestazioni inferiori perché i dati utilizzati da qualsiasi vista dovranno essere caricati anche per le viste che non ne hanno bisogno.
Johannes Brodwall

11
Penso che l'inquinamento funzioni al contrario. Se ho bisogno di caricare ansiosamente i dati, il livello logico (o peggio il livello di accesso ai dati) deve sapere in che modo verrà visualizzato un oggetto. Cambia la visualizzazione e finisci per caricare cose che non ti servono o per perdere oggetti di cui hai bisogno. Un'eccezione di ibernazione è un bug e avvelena quanto qualsiasi altra eccezione inaspettata. Ma le prestazioni sono un problema. I problemi di prestazioni e scalabilità ti costringeranno a dedicare più attenzione e lavorare al tuo livello di accesso ai dati e, eventualmente, a forzare la chiusura anticipata della sessione
Jens Schauder

1
@ JensSchauder "Cambia la visualizzazione e finisci per caricare cose che non ti servono o perderti oggetti che ti servono". È esattamente questo. Se modifichi la visualizzazione, è molto meglio caricare cose che non ti servono (poiché è più probabile che tu sia ansioso di recuperarle) o capire gli oggetti mancanti come si otterrebbe l'eccezione di caricamento lento, piuttosto che caricare la visualizzazione pigramente come questo si tradurrà nel problema N + 1, e non saprai nemmeno che sta accadendo. Quindi IMO è meglio che il livello di servizio (e tu) sappia cosa viene inviato rispetto alla visualizzazione che si carica pigramente e tu non ne sai nulla.
Jeshurun

40

Per una descrizione più lunga, puoi leggere il mio articolo Open Session In View Anti-Pattern . Altrimenti, ecco un riepilogo del motivo per cui non dovresti usare Open Session In View.

Open Session In View ha un approccio sbagliato per il recupero dei dati. Invece di lasciare che il livello aziendale decida come è meglio recuperare tutte le associazioni necessarie dal livello di visualizzazione, forza il contesto di persistenza a rimanere aperto in modo che il livello di visualizzazione possa attivare l'inizializzazione del proxy.

inserisci qui la descrizione dell'immagine

  • Il OpenSessionInViewFilterchiama il openSessionmetodo del sottostante SessionFactorye ottiene una nuova Session.
  • Il Sessionè legato al TransactionSynchronizationManager.
  • Le OpenSessionInViewFilterchiamate doFilterdel javax.servlet.FilterChainriferimento all'oggetto e la richiesta viene ulteriormente elaborata
  • La DispatcherServletsi chiama, e instrada la richiesta HTTP al sottostante PostController.
  • Le PostControllerchiamate i PostServiceper ottenere una lista di Postentità.
  • La PostServiceapre una nuova transazione, e lo HibernateTransactionManagerriutilizza lo stesso Sessionche è stato aperto dal OpenSessionInViewFilter.
  • Il PostDAOrecupera l'elenco delle Postentità senza inizializzare alcuna associazione pigra.
  • Il PostServicecommit della transazione sottostante, ma Sessionnon è chiuso perché è stato aperto esternamente.
  • Le DispatcherServletpartenze di rendering dell'interfaccia utente, che, a sua volta, naviga le associazioni pigri e fa scattare la loro inizializzazione.
  • Il OpenSessionInViewFiltergrado di chiudere la Session, e la connessione al database sottostante è rilasciato come pure.

A prima vista, questa potrebbe non sembrare una cosa terribile da fare, ma, una volta visualizzata dalla prospettiva di un database, una serie di difetti iniziano a diventare più evidenti.

Il livello di servizio apre e chiude una transazione di database, ma in seguito non è in corso alcuna transazione esplicita. Per questo motivo, ogni istruzione aggiuntiva emessa dalla fase di rendering dell'interfaccia utente viene eseguita in modalità di commit automatico. Il commit automatico mette sotto pressione il server del database perché ogni istruzione deve scaricare il log delle transazioni su disco, causando quindi molto traffico di I / O sul lato database. Un'ottimizzazione sarebbe contrassegnare Connectioncome di sola lettura, il che consentirebbe al server database di evitare la scrittura nel registro delle transazioni.

Non c'è più separazione delle preoccupazioni perché le istruzioni vengono generate sia dal livello di servizio che dal processo di rendering dell'interfaccia utente. La scrittura di test di integrazione che affermano il numero di istruzioni generate richiede il passaggio a tutti i livelli (Web, servizio, DAO), mentre l'applicazione viene distribuita su un contenitore Web. Anche quando si utilizza un database in memoria (ad esempio HSQLDB) e un server web leggero (ad esempio Jetty), questi test di integrazione saranno più lenti da eseguire rispetto a se i livelli fossero separati e i test di integrazione back-end usassero il database, mentre il i test di integrazione front-end deridevano del tutto il livello di servizio.

Il livello dell'interfaccia utente è limitato alla navigazione nelle associazioni che possono, a loro volta, attivare N + 1 problemi di query. Sebbene Hibernate offra il @BatchSizerecupero di associazioni in batch e FetchMode.SUBSELECTper far fronte a questo scenario, le annotazioni influenzano il piano di recupero predefinito, quindi vengono applicate a ogni caso d'uso aziendale. Per questo motivo, una query del livello di accesso ai dati è molto più adatta perché può essere adattata ai requisiti di recupero dei dati del caso d'uso corrente.

Ultimo ma non meno importante, la connessione al database potrebbe essere mantenuta durante la fase di rendering dell'interfaccia utente (a seconda della modalità di rilascio della connessione), il che aumenta il tempo di lease della connessione e limita il throughput complessivo della transazione a causa della congestione nel pool di connessioni del database. Più la connessione viene mantenuta, più altre richieste simultanee attenderanno per ottenere una connessione dal pool.

Quindi, o si mantiene la connessione troppo a lungo, si acquisiscono / rilasciano più connessioni per una singola richiesta HTTP, mettendo quindi sotto pressione il pool di connessioni sottostante e limitando la scalabilità.

Spring Boot

Sfortunatamente, l' opzione Apri sessione in visualizzazione è abilitata per impostazione predefinita in Spring Boot .

Quindi, assicurati application.propertiesdi avere la seguente voce nel file di configurazione:

spring.jpa.open-in-view=false

Questo disabiliterà OSIV, in modo che tu possa gestirlo nel LazyInitializationExceptionmodo giusto .


3
L'uso di Open Session in View con l'auto-commit è possibile ma non nel modo in cui era inteso dagli sviluppatori di Hibernate. Quindi, sebbene Open Session in View abbia i suoi svantaggi, l'auto-commit non è uno di questi perché puoi semplicemente spegnerlo e continuare a usarlo.
stefan.m

Stai parlando di ciò che accade all'interno di una transazione, ed è vero. Ma la fase di rendering del livello Web avviene al di fuori di Hibernate, quindi si ottiene la modalità autocommit. Ha senso?
Vlad Mihalcea

Penso che sia una variante che non è quella ottimale per Open Session in View. La sessione e la transazione dovrebbero rimanere aperte fino al rendering della vista, quindi non è necessaria la modalità autocommit.
stefan.m

2
La sessione resta aperta. Ma la transazione no. Anche lo spanning della transazione durante l'intero processo non è ottimale poiché aumenta la sua lunghezza e i blocchi vengono mantenuti più a lungo del necessario. Immagina cosa succede se la vista genera un'eccezione RuntimeException. La transazione verrà annullata perché il rendering dell'interfaccia utente non è riuscito?
Vlad Mihalcea

Grazie mille per la risposta molto dettagliata! Alla fine cambierei la guida solo poiché gli utenti di avvio primaverile probabilmente non useranno jpa in quel modo.
Skeeve

24
  • le transazioni possono essere salvate nel livello di servizio - le transazioni non sono correlate a OSIV. È il Sessionche rimane aperto, non una transazione - in esecuzione.

  • se i livelli dell'applicazione sono distribuiti su più macchine, praticamente non è possibile utilizzare OSIV: è necessario inizializzare tutto ciò di cui si ha bisogno prima di inviare l'oggetto via cavo.

  • OSIV è un modo simpatico e trasparente (ovvero nessuno dei tuoi codici è a conoscenza del fatto che accada) per sfruttare i vantaggi in termini di prestazioni del caricamento lento


2
Per quanto riguarda il primo punto elenco , questo almeno non è vero per l' OSIV originale dal wiki di JBoss, gestisce anche la demarcazione delle transazioni attorno alla richiesta.
Pascal Thivent,

@PascalThivent Quale parte te l'ha fatto pensare?
Sanghyun Lee

13

Non direi che Open Session In View sia considerata una cattiva pratica; cosa ti dà questa impressione?

Open-Session-In-View è un approccio semplice alla gestione delle sessioni con Hibernate. Perché è semplice, a volte è semplicistico. Se hai bisogno di un controllo dettagliato sulle tue transazioni, come avere più transazioni in una richiesta, Open-Session-In-View non è sempre un buon approccio.

Come altri hanno sottolineato, ci sono alcuni compromessi con OSIV: sei molto più incline al problema N + 1 perché è meno probabile che ti rendi conto di quali transazioni stai avviando. Allo stesso tempo, significa che non è necessario modificare il livello di servizio per adattarsi a piccoli cambiamenti nella visualizzazione.


5

Se stai usando un contenitore Inversion of Control (IoC) come Spring, potresti voler leggere l' ambito dei bean . Essenzialmente, sto dicendo a Spring di darmi un Sessionoggetto Hibernate il cui ciclo di vita copre l'intera richiesta (cioè, viene creato e distrutto all'inizio e alla fine della richiesta HTTP). Non devo preoccuparmi LazyLoadExceptionné per chiudere la sessione poiché il contenitore IoC lo gestisce per me.

Come accennato, dovrai pensare ai problemi di prestazioni di N + 1 SELECT. Puoi sempre configurare la tua entità Hibernate in seguito per eseguire il caricamento di join desiderosi in luoghi in cui le prestazioni sono un problema.

La soluzione del bean scoping non è specifica di Spring. So che PicoContainer offre la stessa capacità e sono sicuro che altri contenitori IoC maturi offrono qualcosa di simile.


1
Si dispone di un puntatore a un'implementazione effettiva delle sessioni di Hibernate rese disponibili nella vista tramite i bean con ambito di richiesta?
Marvo

4

Nella mia esperienza, OSIV non è così male. L'unico arrangiamento che ho fatto è utilizzare due diverse transazioni: - la prima, aperta nel "livello di servizio", dove ho la "logica di business" - la seconda aperta appena prima del rendering della vista


3

Ho appena scritto un post su alcune linee guida su quando utilizzare la sessione aperta in vista nel mio blog. Dai un'occhiata se sei interessato.

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


1
Come regola generale SO, se si fornisce una risposta, è meglio fare di più che collegarsi altrove. Forse fornire una o due frasi o elementi elencati per dare il succo. Va bene il collegamento, ma vuoi fornire un piccolo valore in più. Altrimenti, potresti semplicemente commentare e inserire il link lì.
Ddestra

vale la pena leggere il link in questa risposta, fornisce una buona guida su quando utilizzare OSIV e non
em

1

Sono arrugginito su Hibernate .. ma penso che sia possibile avere più transazioni in una sessione di Hibernate. Quindi i limiti della transazione non devono essere gli stessi degli eventi di avvio / arresto della sessione.

OSIV, imo, è principalmente utile perché possiamo evitare di scrivere codice per avviare un "contesto di persistenza" (aka sessione) ogni volta che la richiesta deve effettuare un accesso al DB.

Nel tuo livello di servizio, probabilmente dovrai effettuare chiamate a metodi che hanno esigenze di transazione diverse, come "Richiesto, Nuovo richiesto, ecc." L'unica cosa di cui hanno bisogno questi metodi è che qualcuno (cioè il filtro OSIV) abbia avviato il contesto di persistenza, quindi l'unica cosa di cui devono preoccuparsi è: "hey dammi la sessione di ibernazione per questo thread .. devo fare un po ' Roba DB ".


1

Questo non aiuterà molto ma puoi controllare il mio argomento qui: * Hibernate Cache1 OutOfMemory con OpenSessionInView

Ho alcuni problemi di OutOfMemory a causa di OpenSessionInView e molte entità caricate, perché rimangono nella cache di Hibernate livello1 e non vengono raccolte in modo indesiderato (carico molte entità con 500 elementi per pagina, ma tutte le entità rimangono nella cache)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.