2013-06-05 86 views
14

我正在使用Hibernate來訪問Oracle數據庫。在存儲過程更新數據庫之後,從數據庫重新加載對象,而不是緩存?

我想我在Hibernate的第一級(或會話)緩存中遇到了一些麻煩。我有代表賬戶的表格:ACCOUNT表,INVOICE表和PAYMENTS表。 在Oracle數據庫中定義了一些過程,因此添加PAYMENT會自動更新關聯的INVOICE和ACCOUNT表中的列。

我的問題是,當我使用Hibernate做一些這樣的:

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()); 

最後斷言會失敗,因爲account.getBalance()回報0.00而非20.00

我希望第二次調用accountDao.get(...)來擊中數據庫,並獲取新的ACCOUNT對象。但是Hibernate似乎已經將緩存中的帳戶對象返回了(當我檢查查找調用的調試輸出時,我看到了number of objects hydrated: 0)。

我假設Hibernate沒有意識到數據庫因存儲過程調用而改變,這就是爲什麼它在緩存中使用對象。

於是我開始思考解決方案。一種是每當保存付款時從hibernate會話緩存中刪除任何ACCOUNT和PAYMENT對象。這將強制數據庫獲取(使用新更新的值)以進行任何ACCOUNT或INVOICE操作。

我試過如下:

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

但休眠跟蹤日誌表明,什麼都沒有發生。我認爲sessionFactory.evict(...)在二級緩存上運行,這是不啓用的,所以沒有任何驅逐。

下一個我試圖驅逐每個實例驅逐從會話緩存中的所有賬戶和發票的對象我能找到的:

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); 
    } 
} 

這似乎是工作,但效率極其低下的,因爲它加載的所有實例進入休眠會話緩存在驅逐它們之前,當我真正想要做的就是驅逐會話中的任何當前實例。

我看不到任何清除指定類型所有對象的第一級緩存的方法,那麼還有哪些其他解決方案可用?

回答

9

您可以使用session.refresh()方法。請參閱文檔中的11.3. Loading an object

+1

感謝。然而,我不確定'refresh()'是否是解決方案。我的理解是'refresh()'對於重新加載由數據庫更新的對象很有用,例如如果我保存了「付款」,那麼如果觸發器修改其狀態,則刷新它。但在我的情況下,當更新付款時,我需要在緩存中的每個現有ACCOUNT和INVOICE對象上調用'refresh'。如何從緩存中獲取每個帳戶和付款對象? –

+0

你可以在這些關聯上設置'cascade =「refresh」'。在這種情況下,每當一個實體刷新時,所有關聯的實體也將被刷新。 – lunr

+0

我可以像你所建議的那樣設置'cascade =「refresh」',並且由於ACCOUNT和INVOICE之間的關聯,這意味着我只需刷新ACCOUNT對象。但是我需要解決的問題是:如何從緩存中獲取ACCOUNT對象,這樣我就可以對每個對象調用'refresh',而不執行'from Account'查詢?例如類似於'getSession()。refresh(Account.class)'(它在API AFAIK中不存在,但這是我所尋求的功能)。 –

5

爲什麼不從數據庫中再次讀取帳戶對象之前就趕走了帳戶對象?我這樣做:

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()); 

而且你必須確保你是不是在REPEATABLE_READ隔離級別。在這種隔離模式下,Spring確保同一事務中的兩次讀取總是返回相同的結果。由於隔離級別優先於緩存逐出,所以緩存逐出不會有任何可見的效果。

還有就是要解決此問題一個方法: 聲明你的事務隔離級別READ_UNCOMMITTED或READ_COMMITTED。如果您使用的是標準方言,則可能遇到着名的「標準JPA不支持自定義隔離級別」異常。在這種情況下,您可以應用以下解決方法: 春季3.3:http://amitstechblog.wordpress.com/2011/05/31/supporting-custom-isolation-levels-with-jpa/ 春季3.2:http://shahzad-mughal.blogspot.com/2012/04/spring-jpa-hibernate-support-for-custom.html

+2

感謝您的評論。我不再在該項目上工作(或在該公司),所以我沒有辦法驗證您提出的解決方案。我曾嘗試在我的原始帖子中驅逐所有帳戶類,但是驅逐實例可能是一種方式。 –

相關問題