2009-05-18 12 views
12

Qual è la migliore pratica?Hibernate + Swing

  • una singola sessione per l'intera applicazione
  • Una sessione per ogni finestra
  • Una sessione per thread, e staccando tutto a caso
  • Qualcos'altro?

Ho cercato su Google e non c'è consenso. Tutti dicono qualcosa e poi lo riprendono. Trovo trascinante una sessione in giro stupido ...

Quindi, qual è l'affare?

+0

Hai abbandonato la domanda? Per lo più, si. – Surya

+0

<> – Tordek

+1

Sono passati 3 anni e non hai ricevuto risposta appropriata. Neanche io. A volte ho la sensazione che anche i ragazzi di JBoss non sappiano come usare il loro prodotto. Io uso solo una sessione per evento. –

risposta

6

Forse una sessione per gestione eventi è l'approccio giusto. Nelle applicazioni Web, di solito creiamo una sessione per richiesta (utilizzando tutte quelle robe OpenSessionInView e così via). Se pensiamo a, ogni richiesta nell'applicazione Web è un'interazione diversa nell'applicazione e, in un'applicazione Swing, ogni evento generato è una diversa interazione nell'applicazione. Se applichiamo gli stessi principi che applichiamo nelle applicazioni Web nelle applicazioni Swing, dovremmo creare una sessione per la gestione degli eventi.

Ma dobbiamo determinare se utilizzare Hibernate è l'opzione migliore ... Il database che stai accedendo è un database locale o remoto? Altre applicazioni condividono lo stesso database? Dovresti prendere in considerazione la creazione di un backend EJB \ HTTP invece di avere le tue applicazioni Swing che accedono direttamente al database. Usando Hibernate suggerisci di avere un database non così semplice, quindi penso che dovresti prendere in considerazione la creazione di un backend EJB \ HTTP.

1

Quando si chiude la sessione, tenere presente che ciò causa il distacco degli oggetti e comporta un ulteriore sovraccarico per ricollegare gli oggetti alla sessione per salvarli o aggiornarli con il database.

Detto questo, sono d'accordo con te che trascinare una sessione in giro non ha molto senso, e dovresti rilasciare la sessione alla fine di ogni transazione. Vorrei portare ad avere un pool di sessioni da cui ogni thread può accedere a una sessione in quanto ne ha bisogno, e rilasciarlo di nuovo al pool quando è fatto. Puoi usare Spring per aiutarti a gestire le sessioni per questo.

1

Dipende davvero dall'app, ma in realtà si desidera solo una sessione che duri quanto la transazione logica sul database. Sono abbastanza sicuro che le sessioni non siano thread-safe, quindi potresti voler considerare la localizzazione del comportamento in una singola classe.

1

Non riesco davvero a vedere alcun motivo valido per cui vorreste fare le cose in modo diverso in un'app con un front-end Swing, quindi un front-end web. In quest'ultimo caso, in genere viene creata una sessione per ogni richiesta, sebbene in molti casi la creazione effettiva della sessione venga gestita in modo trasparente (ad esempio, per Spring).

Supponendo che l'app Swing implementa il pattern MVC, allora perché non creare la Session all'inizio di ogni metodo di controller e chiuderla alla fine. Se la tua app è multithreading, puoi utilizzare una variabile ThreadLocal per applicare facilmente ogni Sessione a un determinato thread. Questo è quasi esattamente analogo a ciò che accade in un contenitore Servlet (ad esempio un'app Web).

0

Preferisco sessione per aggiornamento del modello. Aiuta a evitare sessioni di lunga durata e aiuta a minimizzare il numero totale di creazioni di sessione.

0

Se si crea un pool di thread, non si paga molto del prezzo per effettuare connessioni per transazione o per richiesta, a seconda di ciò che si sta facendo.

Tendo a dividere il mio codice al livello inferiore in una classe che si occupa di una tabella o di qualche altro oggetto, e poi sopra che sarà un controller, che sa come parlare a più daos. Ad esempio, per posizionare un ordine di un libro, controllare se è disponibile, quindi elaborare il pagamento, quindi decrementare l'oggetto e effettuare l'ordine. Queste sarebbero una transazione che si estende su più daos, quindi dovrebbero usare la stessa sessione.

Tuttavia, l'utilizzo di una connessione sarebbe problematico, poiché si sta facendo una connessione per un po 'e si potrebbe perdere la connessione, pur presupponendo che esista ancora. Lo stesso problema sarebbe aggravato se una richiesta per schermo o pagina.

È possibile consultare alcune best practice degli utenti Spring, poiché Hibernate sembra essere molto popolare con tale framework.

1

Best Practice (la tua situazione potrebbe essere diversa): Il modello più comune in un client di applicazioni multi-utente/server è session-per-richiesta. In questo modello, una richiesta dal client viene inviata al server (dove viene eseguito il livello di persistenza di Hibernate), viene aperta una nuova sessione di ibernazione e tutte le operazioni di database vengono eseguite in questa unità di lavoro. Una volta che il lavoro è stato completato (e la risposta per il cliente è stata preparata), la sessione viene svuotata e chiusa. Si utilizzerà anche una singola transazione di database per soddisfare la richiesta del client, iniziando e confermandola quando si apre e si chiude la sessione. Il rapporto tra i due è uno-a-uno e questo modello è perfetto per molte applicazioni.

vedere: http://docs.jboss.org/hibernate/stable/core/reference/en/html_single/#transactions-basics-uow

2

Cosa si vuole risolvere è la sincronizzazione di due modelli: Hai il modello in-memory e il modello di database. La semplice propagazione di ogni modifica nel database, come accade, è troppo costosa. Inoltre, si desidera essere in grado di gestire gli errori (ad esempio, ripristinare il modello in uno stato coerente). Per essere in grado di farlo, devi avere un modo per dire "ora, è coerente".

La soluzione corrente è avviare una transazione nel database quando il modello è noto per essere coerente (in genere prima di iniziare a fare le modifiche) e quindi eseguire tutte le modifiche nella modalità in memoria, mapparle in qualche modo sul database , aggiorna il database e quindi effettua il commit della transazione.

Mentre suona semplice, la programmazione OO interferisce attivamente. Nascondiamo le operazioni dei modelli in profondità nella struttura delle chiamate, proviamo davvero a fare in modo che gli utenti di un pezzo di codice non sappiano cosa fa effettivamente il codice. In un mondo perfetto, i tuoi strumenti di sviluppo dovrebbero srotolare tutto il codice necessario a un'operazione in un unico metodo/funzione, avvolgerlo in una transazione e completarlo.

Questo non funziona. Invece, abbiamo deciso di introdurre una variabile globale: la sessione. Il che è male, e ci vergogniamo, quindi cerchiamo di nascondere questo fatto, ma la sessione è globale - per operazione. Ora è necessario un modo per allegare la sessione all'operazione. Puoi dire "tutto il codice che viene eseguito nel thread corrente è un'operazione". Se lo fai, la soluzione naturale è rendere la sessione globale per thread.

Oppure hai qualche token. Quindi allegerai la sessione al token e la passerai in giro.

Ma il problema fondamentale è e sempre stato: come collegare una sessione a una singola operazione sul modello. Le parti difficili devono sapere quando inizia un'operazione, quando finisce e come gestire gli errori.

Per le applicazioni Web, l'utilizzo di una richiesta è il modo naturale per definire un'operazione: tutto ciò che accade durante una richiesta viene considerato un singolo passaggio.L'app in realtà non mantiene il modello in memoria; tutto viene dimenticato alla fine della richiesta e caricato nuovamente dal database quando arriva la richiesta successiva. Lento ma gestibile.

Le applicazioni desktop sono un tipo di bestia completamente diverso. Di solito tengono in memoria l'intero modello per tutto il tempo. Non persistiamo i cambiamenti a meno che l'utente non lo chieda (quando "salva" il suo lavoro) perché sarebbe troppo lento e, poiché non c'è nulla come una richiesta, non esiste un modo semplice e automatico per definire una "operazione" .

L'idea di associare un'operazione a un evento è buona. Solo nelle app desktop è possibile avere più thread che comunicano con gli eventi e ora è necessario un modo per contrassegnare tutti gli eventi come "appartengono all'operazione avviata con l'evento X ricevuto molto tempo fa". Gli eventi sono in genere piccole quantità immutabili di dati, quindi non è possibile allegare la sessione a loro. Ma hai bisogno di una sorta di gettone per segnare tutti gli eventi che appartengono insieme. Questo è il motivo per cui la maggior parte delle app desktop funziona con un server (che funziona di nuovo come un'applicazione web) e senza un grande modello in memoria o non usa un database ma salva il modello in un formato personalizzato (pensa Office).

+0

Devo dire che questa è un'ottima definizione del problema. Ma non una soluzione (anche se una definizione del problema aiuta a ottenere una soluzione). Grazie. – Tordek

+0

Poiché i computer sono ancora così lenti oggi che dobbiamo preoccuparci delle prestazioni, non esiste ancora una soluzione generale. –

6

Ho usato Hibernate in un'applicazione Rich Internet di dimensioni enterprise, che per tecnica assomiglia molto a una combinazione di letargo + swing. Ho passato molto tempo a studiare i diversi modelli di progettazione per l'utilizzo di ibernazione in un'applicazione a 3 livelli.

Hibernate non è esattamente pensato per questo tipo di applicazione, quindi non c'erano molte informazioni disponibili su questo argomento. Ho provato diversi schemi di progettazione, ma la maggior parte di essi ha portato a perdite di memoria, problemi di collegamento e disaccoppiamento degli oggetti nelle sessioni, problemi con le transazioni ecc. La soluzione finale era l'utilizzo di un modello sessione per richiesta. Fondamentalmente, ad ogni richiesta effettuata dalla logica dell'interfaccia utente alla logica aziendale, viene creata una nuova sessione di sospensione. La logica aziendale esegue quindi tutto ciò che si desidera che faccia e, prima di terminare l'esecuzione della business logic, si esegue il flush e si chiude la sessione aperta. In questo modo non ci saranno perdite di memoria, gli oggetti saranno attaccati correttamente e scollegati dalle sessioni.

Questa non è una soluzione perfetta, poiché si verificheranno alcuni problemi, ad esempio con il caricamento e le transazioni lazy. Spiegherò brevemente questi problemi e come risolverli.

Transazioni
Poiché si termina la sessione di ibernazione dopo ogni richiesta, non è possibile avere transazioni che vadano oltre una richiesta. Questo può essere un po 'problematico, ad esempio, supponiamo di voler memorizzare 10 oggetti all'interno della stessa transazione. Non è possibile effettuare una richiesta di salvataggio separatamente per ogni oggetto, poiché la transazione viene interrotta. Pertanto è necessario creare un metodo che prenda come input un elenco di oggetti e salvi tutti quegli oggetti all'interno della stessa transazione. Se la transazione fallisce, si esegue il rollback di tutti gli oggetti.

Lazy loading
Il caricamento lazy degli oggetti non funzionerà perché è probabile che non siano collegati a una sessione (ovvero, se si scarica qualcosa quando la sessione è stata interrotta). Per il caricamento lento al lavoro, è necessario ricollegare gli oggetti alla sessione. Ho trovato anche una soluzione per questo, che è un po 'complicato quando si creano nuovi oggetti entità, ma funziona bene nello sviluppo quotidiano. L'idea è di avere getter e setter duplicati per un campo che è pigro caricato. Una coppia è privata e l'altra è pubblica. L'idea è che la coppia getter/setter privata è quella che l'ibernazione usa internamente e i getter e setter pubblici sono transitori e usati dallo sviluppatore. Quindi, ciò che accade è che quando lo sviluppatore chiama il getter pubblico, il getter controlla se il campo è già stato caricato, se non lo è, quindi collega l'oggetto a una sessione, carica il campo e chiude la sessione. Voilá, tutto funziona e lo sviluppatore non ha mai notato nulla.Ecco un piccolo codice per darvi l'idea:


@Entity 
public class Foo { 
    List<Bar> bars = new ArrayList<Bar>(); 

    @OneToMany 
    private List<Bar> getBarsInternal() { 
    return bars; 
    } 

    private void setBarsInternal(List<Bar> bars) { 
    this.bars = bars; 
    } 

    @Transient 
    public List<Bar> getBars() { 
    // pseudo code 
    if(check if bar is still lazy loaded) { 
     // the field is still lazy loaded 
     // attach the field to a session an initialize it 
     // once the field is initialized, it can be returned 
    } 
    return getBarsInternal();  
    } 

    public void setBars(List<Bar> bars) { 
    setBarsInternal(bars); 
    } 
} 
0

Poiché la creazione della sessione è costoso in Oracle, e io uso soprattutto Oracle come RDBMS, mi piacerebbe andare per "una singola sessione per l'intera applicazione". "Una sessione per gestione eventi" sembra ridicola nel contesto di un'applicazione GUI. Ci sono motivi per farlo sul web, ma questi motivi non si applicano normalmente a un programma GUI.

+2

Le sessioni di ibernazione sono abbastanza economiche, utilizzando una sessione per applicazione si verificherà una perdita di memoria :( –

+0

Ti piacerebbe elaborare perché la creazione di sessioni è così costosa in Oracle? Io uso Hibernate con Oracle e non ho notato problemi di prestazioni importanti, ma Non faccio nulla di troppo pesante e il nostro DB è molto veloce, quindi potrebbe nascondere tutti i problemi – Pavel

0

Quello che si vuole veramente evitare è lo scenario sessione per operazione, dove per ogni singola operazione di database viene creata e chiusa una nuova sessione di Hibernate.

MODIFICA: Ignora le mie assurdità precedenti su una sessione per tutta la durata dell'app, è del tutto possibile (e probabilmente il modo più efficiente) di farlo. Assicurati solo di delimitare le tue transazioni in modo appropriato, in modo dichiarativo usando qualcosa come Spring @Transaction o programmaticamente.

+0

Non vedo come una sessione per tutta la durata del programma possa impedirti di eseguire un commit ogni volta che una richiesta è stato elaborato, quindi perché dovrebbe esserci una grande perdita di dati? –

+0

Hai ragione, in realtà, che non dovrebbe impedirti di utilizzare una sessione. – Pavel

5

Abbiamo iniziato con una sessione-per-app, ho elencato le motivazioni e le questioni al di sotto,

Come suggerisce il nome l'interazione dell'utente tutta con l'app sarebbe costituito da una singola sessione di lunga vita e tutto (principale) la comunicazione con il database avverrebbe attraverso questa sessione. La vostra "unità di lavoro" comprenderebbe ora tutte le interazioni dell'utente con l'applicazione ... il che significa che la vostra "transazione applicativa" è l'intera vita dell'applicazione.

Perché? 1. Il driver più grande per farlo sembra essere la capacità di utilizzare le funzionalità di pigmentazione dell'ibernazione, ad esempio una finestra di dialogo che mostra le informazioni del libro può popolare dinamicamente le informazioni dell'autore quando la casella combinata a discesa è selezionata.

  1. Poiché tutte le modifiche ai dati sottostanti vengono eseguite tramite questa singola sessione di lunga durata, è possibile demarcare esplicitamente i contorni delle transazioni in posizioni convenienti.

  2. Sfrutta la cache di sessione senza dover caricare/scaricare oggetti ogni volta dal DB.

Come:

Se si esegue tutto il lavoro principale in filo GUI (quasi tutti i sistemi GUI Java sono singolo evento filettato è possibile utilizzare Tweak filo modello locale per ottenere la sessione principale da una GUI . filo

Problemi:

vantaggi di cui sopra potrebbe dipingere un quadro roseo ma utilizzando sessioni di corsa lunghe senza molta accortezza migh Ciò causa alcuni problemi seri che potrebbero non essere evidenti fino a quando l'applicazione non diventa abbastanza grande o complessa.

  1. La vostra applicazione potrebbe soffrire di”cache di sessione gonfio” phenomenon..with ogni vostra applicazione ottenendo più lento e più lento con la cache di sessione cresce sempre più grandi, come Hibernate deve passare attraverso tutti gli oggetti nella cache di sessione per ogni query (con una sessione enorme questo potrebbe rallentare, Se la sessione diventa grande, lo svuotamento richiederà sempre più tempo, anche se non ci sono cambiamenti). In sostanza, il vantaggio n. 3 sopra riportato può diventare uno svantaggio (a causa dell'ibernazione di write-behind transazionale) se la sessione di lavoro consentisse di crescere in grande. Una soluzione alternativa che potrebbe essere utile in alcuni casi potrebbe essere l'uso di query.setReadOnly (true), sessione.setReadOnly() per query/entità che caricano oggetti readonly nella sessione.
  2. "sfratto" manuale per mantenere coerente la sessione, Potresti aver usato query SQL per vari motivi in ​​vari punti dell'app per eseguire aggiornamenti/eliminazioni in tal caso hai fatto sfratti manuali per evitare che la sessione fosse corrotta.
  3. Il write-behind transazionale potrebbe interferire (a seconda di come si gestisce la sessione di lunga durata) se si prevede di accedere ai dati da "altre sessioni".
  4. L'utente potrebbe sperimentare il blocco della GUI quando l'ibernazione sta facendo la sua magia lazyloading.
  5. Classic lazy loading n + 1 problemi di query (vedere i collegamenti seguenti)
  6. Problemi di dati obsoleti .se i dati vengono aggiornati da qualche lavoro in background o thread che non utilizza la sessione principale .. la vostra sessione principale non sarà aggiornata con i cambiamenti.

Continuo a pensare che se si dispone di una strategia di sgombero consistente, ad esempio sfrattare il grafico dell'ordine quando la finestra dell'ordine è chiusa, questa potrebbe essere una buona strategia.

Siamo passati alla sessione per finestra, che aveva i suoi problemi come la gestione della comunicazione tra finestre (ad esempio le modifiche nella finestra dei prezzi potrebbero rendere obsoleti i dati della finestra degli ordini). Potrebbe voler guardare l'esempio di swing di Christian Bauer che usa questo approccio.

Immagino che, come per tutto il resto, non ci sia un solo modo corretto per farlo, come si gestisce la sessione dipende da come è disposta l'applicazione, ad esempio, se ogni finestra può essere un'unità di lavoro indipendente, che non interferisce con altre unità di lavoro, quindi la sessione per finestra potrebbe essere un approccio migliore.

0

Uso una sessione per serie di azioni del database che possono essere racchiuse in una transazione, questo include il passaggio di raccolte di oggetti ogni volta che è possibile, ma il problema per la sessione chiusa per campi/proprietà pigri continua a crescere.

My Applications sono ibridi di Swing e JavaFX e in questo modo li ho fissato:

Il mio primo approccio non è stato quello di avere alcun lazy loading in uso, ma questo si è rivelato essere un incubo per alcune delle Entità con Grafici oggetto più complicati.

Il controllo Vista tabella da JavaFX svolge un ruolo importante nelle mie app, penso che lo swing JTable risulti rispetto al TableView su JavaFX.

Ora, quando carico un JFrame o un JInternalFrame o un JDialog, ho precaricare la più recente 'lavorato su' entità, tutti i loro figli sono caricati con Lazy Loading, non perfomance ha colpito, mi legano i dati al TableView controllo con i campi principali desiderosi del nucleo primario che sono visibili. Un utente è tenuto a selezionare un record dalla griglia di TableView su cui lavorare, quando lo fa prendo di nuovo l'istanza dell'entità dal momento che ho un handle per il suo Id, dopo che l'oggetto è stato recuperato, quindi .size() viene invocato il metodo per tutte le raccolte di figli caricate pigramente. Ciò impone Hibernate per caricare i campi/proprietà lazy.

Ora posso fare tutto ciò che voglio con una particolare istanza di un oggetto e tutti i bambini sono popolati.

Penso che questo approccio migliori le prestazioni due volte. Quando recuperi i dati dell'elenco, è veloce. Quando si recupera un oggetto con un ID, il suo punto pin, e dal momento che i bambini sono tutti pigri carichi significa che i selettivi sono anche leggeri e più compatti.