2013-01-07 77 views
3

我面對類似於Invalidating JPA EntityManager session描述的一個問題:使用JPA時避免來自數據庫的陳舊數據?

問題:讓舊數據

我們正在運行JPQL的查詢也同時由不同的應用程序更改SQL數據庫上。我們使用在Tomcat下運行的JSF + Spring + EclipseLink。

類做JPQL查詢是一個單獨的Spring bean,並使用注射EntityManager

@Component 
public class DataRepository{ 
    @PersistenceContext 
    private EntityManager entityManager; 
    public List<MyDTO> getStuff(long id) { 
     String jpqlQuery ="SELECT new MyDTO([...])"; 
     TypedQuery<MyDTO> query = entityManager.createQuery(jpqlQuery,MyDTO.class); 
     return query.getResultList(); 
    } 
[...] 

(代碼轉述)。

問題是此代碼沒有看到直接在數據庫上執行的更改。如果Tomcat實例重新啓動,這些更改只會變得可見。

我們已經嘗試

我們假設該行爲是由相關的EntityManager一級高速緩存中引起的,如鏈接的問題進行說明。我們發現了兩個解決方案:

  • 呼叫entityManager.clear()調用createQuery之前(這是建議的鏈接的問題)
  • 注入的EntityManagerFactor(使用@PersistenceUnit),然後創建並關閉新EntityManager爲每個查詢

這兩種解決方案都能做到我們想要的 - 我們獲得新的數據。

問題:

  • 是這兩種解決方案是否正確?哪一個更好?
  • 特別是,我們可以安全地在注入的EntityManager上調用entityManager.clear(),或者這會以某種方式影響也使用注入的EntityManager的其他代碼(在相同或不同的類中)?
  • 有沒有不同的更好的方法?我們可以以某種方式聲明我們想刷新緩存,或者我們想要一個新的EntityManager

我想這一定是一個相當普遍的問題(因爲它發生時多個應用程序共享一個數據庫),所以我想必須有一個簡單的解決方案...

+0

我認爲這不是很常見的共享與多個應用程序的數據庫?這聽起來像你會不斷重新加載你的EntityManager而受到嚴重懲罰。你不能通過服務提供所需的數據嗎?或者在應用程序之間使用一些共享緩存? – vertti

+0

@vertti:好點。我沒有意識到JPA在使用共享數據庫時存在這樣的問題。當然,使用服務或類似服務是可能的,但這意味着一個重大變化,而且不太可能發生。 – sleske

回答

2
  1. 創建新EntityManager小號是正確的,另一個不是(見下文)。

  2. 調用EntityManager#clear()對於除單線程系統之外的所有系統都是非常危險的。

    ...導致所有被管實體分離...

    因此,如果一個線程與附屬實體一起工作,而「您的」線程清除實體管理器,則會產生嚴重的副作用。

  3. 嗯,很難說。如果在您的應用程序之外修改的實體數量很少,我會直接使用數據源,並針對相應的操作使用JdbcTemplate

+0

EntityManagers不是線程安全的,因此無論如何您都不應該在兩個線程中使用同一個EntityManager。每個線程都應該有自己的EntityManager注入,因爲它們表示不同的事務上下文。 – Chris

+0

是的。只是看看OP的代碼片段,以確保他確實以這種方式使用它。 –

+0

@克里斯:是的,沒錯。然而,我們使用Spring來注入EntityManager,並且Spring爲我們提供了「一個共享的,線程安全的代理,用於實際的事務性EntityManager」(http://static.springsource.org/spring/docs/3.2.x/spring-framework -reference/html/orm.html#orm-jpa-straight)。所以線程安全不是問題。 – sleske

4

看起來我們已經解決了這個問題。

實際上有兩個問題:

  1. 在調試問題,在某些情況下,我們並沒有使用新的EntityManager,每次查詢。當使用和重新使用相同的EntityManager時,實體顯然會從一旦檢索到它們就不刷新(除非使用EntityManager.refresh()明確刷新)。這顯然是因爲一旦一個實體被加載,它就被存儲在EntityManager的持久性上下文(又名一級緩存)中。這樣做是必要的,因爲JPA規範要求對同一個實體的後續查詢返回相同的對象實例。換句話說:只要你使用相同的EntityManager,你會得到陳舊的數據,除非你明確地刷新。

  2. 當我們確實爲每個查詢使用一個新的EntityManager時,通常情況下工作。但是,我們遇到了a bug in EclipseLink,其中針對某些JPQL查詢返回陳舊數據(涉及構造函數表達式和JOIN FETCH)。

短版

如果你想永遠從自修改程序之外的數據庫得到的最新數據,然後

  • 使用新鮮的EntityManager對於每個查詢,你想要新數據
  • 禁用二級緩存(<shared-cache-mode>NONE</shared-cache-mode>位於persistence.xml中)
0

這將做魔術 entityManager.getEntityManagerFactory().getCache().evict(MyDTO.class);