2013-06-05 18 views
14

Sto utilizzando Hibernate per accedere a un database Oracle.Ricaricamento di oggetti dal database, non dalla cache, dopo il database degli aggiornamenti delle stored procedure?

Penso di avere qualche problema con la cache di primo livello (o sessione) di Hibernate. Dispongo di tabelle che rappresentano gli account: la tabella ACCOUNT, la tabella INVOICE e la tabella PAYMENTS. Esistono procedure definite nel database Oracle in modo che l'aggiunta di un pagamento aggiorni automaticamente le colonne nelle tabelle INVOICE e ACCOUNT associate.

Il problema che ho è quando uso Hibernate di fare qualcosa di simile al seguente:

Account account = accountDao.get(accountId); 
assertEquals(0.00, account.getBalance()); 

// Saving a payment will trigger stored procedures that 
// will update the account balance. 
paymentDao.save(createPaymentForAccount(accountId, 20.00)); 

account = accountDao.get(accountId); 
assertEquals(20.00, account.getBalance()); 

L'affermazione finale non riuscirà perché account.getBalance() rendimenti 0.00 piuttosto che 20.00.

Voglio la seconda chiamata a accountDao.get(...) per colpire il database e recuperare il nuovo oggetto ACCOUNT. Ma Hibernate sembra restituire l'oggetto account già nella sua cache (quando ispeziono l'output di debug per la chiamata di ricerca, vedo number of objects hydrated: 0).

Suppongo che Hibernate non sia a conoscenza del fatto che il database è stato modificato a causa della chiamata alla procedura memorizzata, motivo per cui utilizza l'oggetto nella sua cache.

Così ho iniziato a pensare alle soluzioni. Uno è rimuovere tutti gli oggetti ACCOUNT e PAYMENT dalla cache della sessione di ibernazione ogni volta che viene salvato un PAGAMENTO. Ciò imporrà un recupero del database (con i valori appena aggiornati) per qualsiasi operazione ACCOUNT o FATTURA.

ho provato la seguente:

public void save(Payment payment) { 
    getSession().persist(payment); 
    getSessionFactory().evict(Invoice.class); 
    getSessionFactory().evict(Account.class); 
} 

Ma il registro di ibernazione traccia ha dimostrato che non è successo niente. Penso che sessionFactory.evict(...) funzioni sulla cache di secondo livello, che non è abilitata e quindi non c'è nulla da sfrattare.

Poi ho provato sfrattare tutti gli oggetti di account e la fattura dalla cache sessione sfrattando ogni istanza che ho trovato:

public void save(Payment payment) { 
    getSession().persist(payment); 
    for (Invoice invoice: lookupInvoices()) { // e.g. "from Invoice" query 
    getSession().evict(invoice); 
    } 
    for (Account account: lookupAccounts()) { // e.g. "from Account" query 
    getSession().evict(account); 
    } 
} 

Questo sembra funzionare, ma è terribilmente inefficiente, perché carica tutti i casi in ibernazione cache di sessione prima di sfrattarli, quando tutto ciò che voglio veramente è rimuovere qualsiasi istanza corrente nella sessione.

Non riesco a vedere alcun modo per cancellare la cache di primo livello di tutti gli oggetti di un tipo specificato, quindi quali altre soluzioni sono disponibili?

risposta

9

È possibile utilizzare il metodo session.refresh(). Vedere 11.3. Loading an object nella documentazione.

+1

Grazie. Non sono sicuro che 'refresh()' sia la soluzione qui, comunque. La mia comprensione è che 'refresh()' è utile per ricaricare un oggetto che viene aggiornato dal database, ad es. se salvi un "pagamento", allora "aggiorna" se i trigger modificano il suo stato. Ma nel mio caso ho bisogno di chiamare "refresh" su ogni oggetto ACCOUNT e FATTURA esistente nella cache quando viene aggiornato un pagamento. Come posso recuperare ogni oggetto ACCOUNT e PAYMENT dalla cache? –

+0

È possibile impostare 'cascade =" refresh "' su queste associazioni. In tal caso ogni volta che un'entità viene aggiornata, anche tutte le entità associate verranno aggiornate. – lunr

+0

Posso impostare 'cascade =" refresh "', come suggerito, e a causa delle associazioni tra ACCOUNT e FATTURA ciò significa che ho solo bisogno di aggiornare gli oggetti ACCOUNT. Ma il problema che devo risolvere è: come ottenere ACCOUNT oggetti dalla cache così posso richiamare "refresh" su ognuno di essi, senza eseguire una query 'from Account'? per esempio. qualcosa come 'getSession(). refresh (Account.class)' (che non esiste nell'API AFAIK, ma questo è il tipo di funzionalità che sto cercando). –

5

Perché non rimuovere l'oggetto account prima di leggerlo di nuovo dal database? Vorrei farlo:

Account account = accountDao.get(accountId); 
assertEquals(0.00, account.getBalance()); 

// Saving a payment will trigger stored procedures that 
// will update the account balance. 
paymentDao.save(createPaymentForAccount(accountId, 20.00)); 

accountDao.getSession().evict(account) 
account = accountDao.get(accountId); 
assertEquals(20.00, account.getBalance()); 

Inoltre è necessario assicurarsi che il proprio non sono in un livello di isolamento REPEATABLE_READ. In questa modalità di isolamento, Spring garantisce che due letture all'interno della stessa transazione restituiranno sempre lo stesso risultato. Poiché il livello di isolamento ha la precedenza sullo sfratto della cache, lo sfratto della cache non avrà alcun effetto visibile.

C'è un modo per aggirare questo comportamento: Dichiarare il livello di isolamento della transazione su READ_UNCOMMITTED o READ_COMMITTED. Potresti imbatterti nella famosa eccezione "Standard JPA non supporta i livelli di isolamento personalizzati" se usi un dialetto standard. In tal caso, è possibile applicare la seguente soluzione: spring 3.3: http://amitstechblog.wordpress.com/2011/05/31/supporting-custom-isolation-levels-with-jpa/ spring 3.2: http://shahzad-mughal.blogspot.com/2012/04/spring-jpa-hibernate-support-for-custom.html

+2

Grazie per il commento. Non sto più lavorando a quel progetto (o a quella compagnia), quindi non ho un modo di verificare la tua soluzione proposta. Ho provato a sfrattare tutte le classi Account nel mio post originale, ma sfrattare l'istanza è probabilmente la strada da percorrere. –

Problemi correlati