Iberna la progettazione dell'applicazione a caricamento lento


87

Tendo a usare Hibernate in combinazione con il framework Spring e le sue capacità di demarcazione dichiarativa delle transazioni (ad esempio, @Transactional ).

Come tutti sappiamo, l'ibernazione cerca di essere il più non invasivo e trasparente possibile, tuttavia questo si rivela un po 'più impegnativo quando si impiegano lazy-loadedrelazioni.


Vedo una serie di alternative di design con diversi livelli di trasparenza.

  1. Crea relazioni non pigre (p. Es., fetchType=FetchType.EAGER)
    • Questo vioalizza l'intera idea di caricamento lento.
  2. Inizializza le raccolte utilizzando Hibernate.initialize(proxyObj);
    • Ciò implica un accoppiamento relativamente alto al DAO
    • Sebbene possiamo definire un'interfaccia con initialize, non è garantito che altre implementazioni forniscano alcun equivalente.
  3. Aggiungi il comportamento della transazione agli Modeloggetti persistenti stessi (utilizzando il proxy dinamico o @Transactional)
    • Non ho provato l'approccio proxy dinamico, anche se non mi è mai sembrato di far funzionare @Transactional sugli oggetti persistenti stessi. Probabilmente a causa di tale ibernazione è un'operazione su un proxy con cui stare.
    • Perdita di controllo quando le transazioni sono effettivamente in corso
  4. Fornisci API pigra / non pigra, ad esempio loadData()eloadDataWithDeps()
    • Forza l'applicazione a sapere quando utilizzare quale routine, ancora una volta accoppiamento stretto
    • Metodo overflow,, loadDataWithA()....,loadDataWithX()
  5. Forza la ricerca delle dipendenze, ad esempio fornendo solo byId()operazioni
    • Richiede molte routine non orientate agli oggetti, ad esempio findZzzById(zid), e quindi getYyyIds(zid)invece diz.getY()
    • Può essere utile recuperare ogni oggetto in una raccolta uno per uno se c'è un grande sovraccarico di elaborazione tra le transazioni.
  6. Fai parte dell'applicazione @Transactional invece del solo DAO
    • Possibili considerazioni sulle transazioni annidate
    • Richiede routine adattate per la gestione delle transazioni (ad esempio, sufficientemente piccole)
    • Piccolo impatto programmatico, anche se potrebbe comportare transazioni di grandi dimensioni
  7. Fornire al DAO profili di recupero dinamici , ad es.loadData(id, fetchProfile);
    • Le applicazioni devono sapere quale profilo utilizzare e quando
  8. Tipo di transazione AoP, ad esempio, intercetta le operazioni ed esegue le transazioni quando necessario
    • Richiede la manipolazione del codice byte o l'utilizzo del proxy
    • Perdita di controllo durante l'esecuzione delle transazioni
    • Magia nera, come sempre :)

Ho perso qualche opzione?


Qual è il tuo approccio preferito quando cerchi di ridurre al minimo l'impatto delle lazy-loadedrelazioni nella progettazione dell'applicazione?

(Oh, e scusa per WoT )


esempio per le opzioni 2 e 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be

Potrebbe fornire un esempio per l'opzione 4?
gradi notte

Risposte:


26

Come tutti sappiamo, Hibernate cerca di essere il più non invasivo e il più trasparente possibile

Direi che l'ipotesi iniziale è sbagliata. La persistenza trasparente è un mito, poiché l'applicazione dovrebbe sempre occuparsi del ciclo di vita dell'entità e della dimensione del grafo oggetto da caricare.

Nota che Hibernate non può leggere i pensieri, quindi se sai che hai bisogno di un particolare insieme di dipendenze per una particolare operazione, devi esprimere le tue intenzioni in Hibernate in qualche modo.

Da questo punto di vista, le soluzioni che esprimono esplicitamente queste intenzioni (vale a dire, 2, 4 e 7) sembrano ragionevoli e non soffrono della mancanza di trasparenza.


Hai ragione ovviamente, il più trasparente possibile funziona solo finora. Queste sono alcune belle scelte che hai scelto.
Johan Sjöberg

IMHO: risposta perfettamente corretta. In effetti, è un mito. A proposito: il mio voto sarebbe per le opzioni 4 e 7 (o allontanarsi del tutto
dall'ORM

7

Non sono sicuro di quale problema (causato dalla pigrizia) tu stia suggerendo, ma per me il problema più grande è evitare di perdere il contesto della sessione nelle cache delle mie applicazioni. Caso tipico:

  • l'oggetto fooviene caricato e inserito in una mappa;
  • un altro thread prende questo oggetto dalla mappa e chiama foo.getBar()(qualcosa che non è mai stato chiamato prima ed è valutato in modo pigro);
  • boom!

Quindi, per risolvere questo problema, abbiamo una serie di regole:

  • avvolgere le sessioni nel modo più trasparente possibile (ad esempio OpenSessionInViewFilterper le webapp);
  • avere API comuni per thread / pool di thread in cui il bind / unbind della sessione db viene eseguito da qualche parte in alto nella gerarchia (racchiuso in try/finally) così le sottoclassi non devono pensarci;
  • quando si passano oggetti tra thread, passare gli ID invece degli oggetti stessi. Il thread ricevente può caricare l'oggetto se necessario;
  • quando si memorizzano oggetti nella cache, non memorizzare mai nella cache gli oggetti ma i loro ID. Avere un metodo astratto nella propria classe DAO o manager per caricare l'oggetto dalla cache di Hibernate di 2 ° livello quando si conosce l'ID. Il costo per il recupero di oggetti dalla cache di Hibernate di 2 ° livello è ancora molto più economico rispetto al passaggio a DB.

Questo, come puoi vedere, in effetti non è neanche lontanamente non invasivo e trasparente . Ma il costo è ancora sopportabile, da confrontare con il prezzo che dovrei pagare per un carico impaziente. Il problema con quest'ultimo è che a volte porta all'effetto farfalla quando si carica un singolo oggetto referenziato, per non parlare di una raccolta di entità. Anche il consumo di memoria, l'utilizzo della CPU e la latenza, per non dire altro, sono di gran lunga peggiori, quindi immagino di poterci convivere.


Grazie per la tua risposta. La perdita di transparencyconsiste nel forzare l'applicazione a preoccuparsi del caricamento di oggetti pigri. Se tutto è stato recuperato con entusiasmo, l'applicazione potrebbe essere completamente ignara del fatto che gli oggetti siano persistiti o meno in un database, poiché Foo.getBar()avrà sempre successo. > when passing objects between threads, pass IDs, sì, questo corrisponderebbe al # 5.
Johan Sjöberg

3

Un modello molto comune consiste nell'usare OpenEntityManagerInViewFilter se stai creando un'applicazione web.

Se stai costruendo un servizio, aprirei il TX sul metodo pubblico del servizio, piuttosto che sui DAO, poiché molto spesso un metodo richiede di ottenere o aggiornare più entità.

Questo risolverà qualsiasi "eccezione di Lazy Load". Se hai bisogno di qualcosa di più avanzato per l'ottimizzazione delle prestazioni, penso che recuperare i profili sia la strada da percorrere.


1
Credo che volevi dire: A antipattern molto comune ... . Anche se sarei d'accordo con l'apertura di TX a livello di servizio, ma l'uso di OSIVè ancora un antipattern e porta a problemi molto seri come l'incapacità di gestire con grazia le eccezioni o il degrado delle prestazioni. Per riassumere: IMHO OSIV è una soluzione accomodante, ma valida solo per progetti di giocattoli.
G. Demecki
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.