2009-08-24 83 views
1

我已經爲我的應用程序編寫了一個測試用例,以查看交易的行爲。而且我發現沒有任何事情按照我認爲的方式工作。JPA和Spring交易 - 請現在解釋

我有一個基於Spring的應用程序,使用Hibernate作爲JPA提供程序,由MySQL支持。 我有DAO對象,擴展了Spring的JpaDaoSupport。 Spring的事務管理涵蓋了這些內容。

我創建這樣作品測試用例: 1)實體被創建,一些計數器設置爲0。 2)然後兩個線程被創建,其中兩個在一個調用DAO方法incrementCounter()循環。我認爲,當事務覆蓋DAO方法時,只有一個線程會在其中(即,Spring將負責同步)。但這已被證明是錯誤的假設。

在(臨時的)通過將​​添加到DAO方法之後,我發現Hibernate不存儲DAO方法所做的更改,而另一個線程在find()實體時具有舊數據。只有明確的電話this.getJpaTemplate().flush();幫助。

我也認爲實體管理器會從持久化上下文的緩存中給我同樣的實體實例,但這也是錯誤的。我已經檢查了hashCode()和equals(),它們都很好 - 基於實體的bussines關鍵字。

歡迎任何評論,因爲它似乎我錯過了JPA/Spring如何處理事務的一些基本概念。

  1. DAO方法應該是​​?
  2. 我應該在每個DAO方法結束時調用flush()嗎?
  3. Spring是否負責使DAO方法的調用具有事務性? (即不讓兩個線程同時在同一個實體中工作)
  4. 如果不是,我該如何實現這個目標?

請注意,在我的測試用例中,我使用了一個單獨的DAO對象,但是應該可以,因爲Spring的bean是單例 - 對嗎?

感謝您的任何幫助。

public class EntityDaoImpl extends JpaDaoSupport implements EntityDao { 

public synchronized void incrementCounter(String znacka) 
{ 

    String threadName = Thread.currentThread().getName(); 
    log.info(threadName + " entering do incrementCounter()."); 

    Entity ent = this.getJpaTemplate().find(Entity.class, znacka); 
    log.info("Found an entity "+ent.getZnacka()+"/"+ent.hashCode()+" - " + ObjectUtils.identityToString(ent)); 
    log.info(threadName + ": Actual count: "+ent.getCount()); 

    ent.setCount(ent.getCount() + 5); 

    int sleepTime = threadName.endsWith("A") ? 700 : 50; 
    try { Thread.sleep(sleepTime); } 
    catch(InterruptedException ex) { } 

    ent.setCount(ent.getCount() + 5); 
    this.getJpaTemplate().flush(); 

    log.info(threadName + " leaving incrementCounter()."); 
} 
} 

沒有​​和flush(),這是給我像輸出

Thread A: Actual count: 220 
... 
Thread B: Actual count: 220 
... 
Thread A: Actual count: 240 
... 
Thread B: Actual count: 250 
... 
Thread A: Actual count: 250 

...等等,這意味着一個線程自動覆蓋從其他的變化。

回答

3
  1. 應該DAO方法進行同步?我通常不會,因爲他們沒有任何狀態。沒必要。
  2. 我應該在每個DAO方法結束時調用flush()嗎?不,我不這樣做。
  3. 是春天負責進行調用DAO方法的行爲事務? (即不讓兩個線程同時在同一個實體中工作)。我認爲你會混淆交易,隔離和同步。
  4. 如果不是,我該如何做到這一點?我認爲你必須擔心同步和隔離。

你的例子不是我認爲的DAO。我認爲你的測試真的不是適當的習慣用法。如果我有一個對象美孚,我就會有一個FooDao界面,將宣佈該對象的CRUD方法:

public interface FooDao 
{ 
    List<Foo> find(); 
    Foo find(Serializable id); 
    void saveOrUpdate(Foo foo); 
    void delete(Foo foo); 
} 

有可能寫一個通用的DAO,正如你從示例猜測。

Spring通常具有使用域和持久對象來實現用例的服務層。這就是需要聲明事務的地方,因爲工作單元與用例而不是DAO相關聯。 DAO無法知道它是否是大型交易的一部分。這就是爲什麼通常在服務上聲明交易的原因。

+0

感謝您的回答。 我開始將用例放到DAO中,因爲在JpaDaoSupport中,DAO對象只是一個用於將引用轉換爲正確類型的瘦shell。如果我理解得很好,用另一層用例,我最終將得到其他Spring管理的bean,實現用例,用Spring事務而不是DAO來執行AOP;所以它在技術上是相同的,不是嗎? 你會推薦什麼方法來照顧同步? 你會推薦一些Spring 2.5 + JPA教程應用程序嗎?謝謝 – 2009-08-24 00:38:21

+0

我已經引用了Spring的成語 - 我建議遵循它,特別是如果這是你第一次與Spring。通常我在Java EE應用服務器(如Tomcat或JBOSS或WebLogic)上運行Spring,因此每個請求都在其自己的線程中運行。應用程序服務器將處理請求排隊。我會推薦Spring參考文檔。 – duffymo 2009-08-24 00:45:06

+0

那麼,我所做的是在Tomcat上運行的Web應用程序的後端。 Spring引用是好的和廣泛的,但仍然不包含同步提示 - 至少我沒有找到。 我猜EntityManager#鎖(對象實體,LockModeType lockMode)是我應該看看,對吧? – 2009-08-24 01:03:59

1

作爲我在大多數應用程序中所做的工作,事務通常在DAO之上的服務層進行聲明。這意味着如果你在不同的實體上進行一些插入,它可以有事務性語義。

問題的答案取決於你的應用程序:

  • 如果你在同一實體期望很高爭的,你可以使用pessimistic locking將獲得一個數據庫鎖,以便其他線程不能超過更新頂端。與您的同步DAO相似,這將限制應用程序的整體性能,並且您需要注意避免死鎖
  • 如果您希望很少出現這種情況,請使用樂觀鎖定,這將保留版本列,並且如果更新丟失了一個異常,在應用程序代碼中被處理。
3

我認爲,當覆蓋着交易DAO方法,那麼只有一個線程會在它裏面

根據您的描述,每次調用incrementCounter應該在自己的事務中運行,但線程併發運行,因此您將有兩個事務同時運行。

(即,Spring將負責同步)。

這聽起來像是你混淆了交易與同步。僅僅因爲兩個線程正在運行他們自己的事務並不意味着這些事務將相對於彼此序列化,除非您的事務隔離級別設置爲SERIALIZABLE。

我還以爲實體管理器會給我同樣的實體實例從持久化上下文

是緩存,持久化上下文的範圍之內。如果您的持久性上下文具有事務範圍,那麼您只能保證在同一個事務中獲得相同的實體實例,即在一次incrementCounter調用中。

應該同步DAO方法嗎?

正如在其他文章中提到的,通常DAO方法是CRUD方法,並且通常不會同步,因爲DAO只是簡單地選擇/更新/ etc。並不知道你在做什麼的大背景。

Spring是否負責使調用DAO方法的行爲是事務性的? (即,不讓兩個線程同時與同一個實體一起工作)

再次,事務!=同步。

如果不是,我該如何做到這一點?

正如其他文章中所提到的,您可以在Java級別執行同步或將您的事務隔離級別設置爲SERIALIZABLE。另一個選擇是使用SELECT FOR UPDATE語法(例如Hibernate中的LockMode.UPGRADE)來通知數據庫你打算進行更改並且數據庫應該鎖定行。

如果你不拘泥於悲觀鎖定,你可以實現樂觀鎖定,例如,使用Hibernate版本控制。你會得到很多樂觀的鎖定失敗與你的具體incrementCounter例子,但人們會認爲一個真正的應用程序不會像那樣。