2010-04-02 14 views
13

Nel riferimento Hibernate, si afferma più volte cheCome interrompere una sessione di sospensione?

Tutte le eccezioni generate dal Hibernate sono fatali. Ciò significa che è necessario ripristinare la transazione del database e chiudere l'attuale Session. Non sei autorizzato a continuare con che funziona con un Session che ha generato un'eccezione.

Una delle nostre applicazioni legacy utilizza una singola sessione per aggiornare/inserire molti record da file in una tabella DB. Ogni aggiornamento/inserimento di record viene eseguito in una transazione separata, che viene quindi confermata (o ripristinata nel caso si verifichi un errore). Quindi per il prossimo record viene aperta una nuova transazione, e così via. Ma la stessa sessione viene utilizzata durante l'intero processo, anche se uno HibernateException è stato catturato durante l'elaborazione. Stiamo utilizzando Oracle 9i btw con Hibernate 3.24.sp1 su JBoss 4.2.

Leggendo quanto sopra nel libro, ho capito che questo progetto potrebbe fallire. Così ho refactored l'app per utilizzare una sessione separata per ogni aggiornamento del record. In un test unitario con una fabbrica di sessioni di simulazione, posso verificare che ora stia richiedendo una nuova sessione per ogni aggiornamento del record. Fin qui tutto bene.

Tuttavia, non abbiamo trovato alcun modo per riprodurre l'errore di sessione durante il test dell'intera app (sarebbe questo uno stress test btw, o ...?). Abbiamo pensato di arrestare l'ascoltatore del DB, ma ci siamo resi conto che l'app ha mantenuto un certo numero di connessioni aperte al DB e il listener non avrebbe influito su quelle connessioni. (Questa è un'app Web, attivata una volta ogni notte da uno scheduler, ma può anche essere attivata tramite il browser.) Poi abbiamo provato ad eliminare alcune di queste connessioni nel DB mentre l'app stava elaborando gli aggiornamenti - questo ha comportato un errore aggiornamenti, ma poi l'app ha continuato felicemente ad aggiornare il resto dei record. Apparentemente Hibernate è abbastanza intelligente da riaprire connessioni rotte sotto il cofano senza interrompere l'intera sessione.

Quindi questo potrebbe non essere un problema critico, in quanto la nostra app sembra essere abbastanza robusta anche nella sua forma originale. Tuttavia, il problema continua a infastidirmi. Vorrei sapere:

  1. In quali circostanze la sessione Hibernate davvero diventare inutilizzabile dopo un HibernateException è stato gettato (Aggiornamento: e quali sono i sintomi)?
  2. Come riprodurre questo in un test (Aggiornamento: preferibilmente in integrazione, piuttosto che test di unità)?
  3. (Qual è il termine corretto per una tale prova?)

risposta

4

In quali circostanze la sessione Hibernate davvero diventare inutilizzabile dopo un HibernateException è stato gettato?

Interessante domanda. Sono abbastanza sicuro di aver visto casi in cui la sessione stava ancora "funzionando" dopo tale eccezione. Il problema è forse che questo non è garantito. Dovrò scavare un po 'di più.

Aggiornamento: Citando this message da uno sviluppatore Hibernate nei forum Hibernate:

La sessione Hibernate dovrebbe essere gettato via quando si verifica alcuna eccezione.Può essere lasciato in uno stato incoerente rispetto al database. Questo non è il caso di una sessione di sola lettura poiché non utilizza alcuna cache di livello di sessione. In alternativa, è possibile cancellare la cache a livello di sessione quando si verifica un'eccezione (ma non mi piace personalmente questo approccio in quanto non è una soluzione pulita e può portare a problemi e problemi futuri).

Quindi il problema non è proprio se lo Session è "tecnicamente utilizzabile" oppure no, il problema è che potrebbe non funzionare come previsto. E tu chiaramente non lo vuoi

Come riprodurre questo in un test?

Si potrebbe deliberatamente violare una condizione che causa una sottoclasse da buttare, ad esempio violando un vincolo di integrità per ottenere un ConstraintViolationException. Che dire di una cosa del genere:

Session session = HibernateUtil.getCurrentSession(); 

try { 
    session.beginTransaction();  
    // execute some code that violates a unique constraint 
    ... 
    session.getTransaction().commit(); 
} catch (HibernateException e) { 
    session.getTransaction().rollback(); 
} 

// keep using the session 
try { 
    session.beginTransaction();  
    ... 
    session.getTransaction().commit(); 
} catch (HibernateException e) { 
    session.getTransaction().rollback(); 
} 
// do we reach this point? 

Ma non sono sicuro che questo dimostrerà nulla. Voglio dire, se la sessione funziona ancora, possiamo solo concludere per questo caso particolare.

Aggiornamento: Dimentica il mio suggerimento, non dimostrerà nulla. In realtà, penso che sarebbe estremamente complicato (tecnicamente e funzionalmente) dimostrare che le cose funzionano come previsto (e che potrebbe ancora essere un "incidente") dopo un HibernateException. Quindi, invece di provare a provare qualcosa, suppongo che la cosa giusta da fare sia seguire la raccomandazione (o l'alternativa suggerita ma richiederei semplicemente un nuovo Session e close per ogni transazione, eccezione lanciata o meno).

Qual è il termine corretto per un test del genere?

Test di integrazione? Test di robustezza?

1

La mia opinione su questo è che

  1. it't quasi impossibile sapere se Hibernate funziona, ha incontrato una situazione inaspettata ed è ora in esecuzione in uno stato indefinito - In sostanza vi sarà invocando UB eseguendo altre operazioni sulla sessione.

  2. Idea - registrare un intercettore e gettare un HibernateException in esso - spero che Hibernate non prenderlo: D iniezione

  3. guasto?

+0

Per quanto riguarda 1), anche questa è la mia comprensione, quindi sono felice di aver reso la nostra app più solida. Il problema diretto è, come verificare questo in un test indipendente? 2) sembra interessante, in quanto ciò potrebbe essere fatto senza modificare l'app stessa. –

3

Interessante. Il modo migliore per dirlo è andare a dare un'occhiata alle fonti di Hibernate.Per quanto ho capito, il Session non porta molta stato in giro, tranne che per la roba seguente:

  • cache di primo livello (vedi StatefulPersistenceContext) - potrebbe essere in realtà rotto dopo il fallimento dell'operazione;
  • coda azioni (vedere ActionQueue) - sembra essere a posto se si esegue il lavaggio manuale;
  • listener di eventi - le loro istanze sono consapevoli della sessione (vedi EventSource) e, come avrete notato, le eccezioni JDBC sono sempre causati da vampate (DefaultFlushEventListener, per essere più precisi), ma una rapida occhiata a AbstractFlushingEventListener mostra che funzionano effettivamente con la sessione JDBCContext e ActionQueue.

E l'ultima cosa, il rollback della transazione stessa non interferisce in alcun modo con lo stato della sessione, vedere JDBCTransaction.

Quindi per quanto mi sono divertito, lo Session in realtà sopravvive a una transazione difettosa, tranne che la cache di primo livello potrebbe essere leggermente imprecisa; per ovviare a questo si potrebbe preferire di chiamare session.refresh o session.load per risincronizzare esplicitamente gli stati.

EDIT: il modo più rapido per testare le eccezioni JDBC è probabilmente session.createSQLQuery("something offensive to SQL parser").executeUpdate() - si otterrà una perfetta SQLGrammarException - e una transazione rotto =)

0

creare una sottoclasse di sessione per il test. Implementalo per generare un'eccezione in uno scenario specifico. Utilizzare una configurazione di ibernazione specifica per il test che imposta l'ibernazione per utilizzare la sessione di test.

+0

Questo inverte una HibernateException, ma non influisce sullo stato della sessione di Hibernate stessa. In altre parole, questa è solo una Sessione fittizia implementata a mano. –

+0

forse non capisco cosa stai cercando allora. stai cercando un modo affidabile per mettere l'ibernazione in uno stato rotto (ad esempio indefinito)? e poi cosa? – james

+0

Sto cercando un modo affidabile per mettere la sessione di Hibernate in uno stato rotto, in modo che la versione precedente della nostra app (che riutilizza la stessa sessione tra le transazioni) fallisca, e la versione più recente (che ottiene una nuova sessione per ogni transazione) passa il test. –

Problemi correlati