聽起來很華麗,用起來也很過癮,但是一開始沒有搞的很清楚,加上又找不到詳細的解釋,在Stackoverflow上問了問題,再參照其它的文章,才搞懂他真正的用法。
本文章的關鍵字是 : Transaction propagation , declarative transaction,Spring宣告式交易管理。
首先先講測試的架構,都是用annotation來做。前面有介紹過方法了。
1。 發起方是一個action裡的method,它會call UserService一個 testAddUserButAddCountryFail的方法。
2. 在這個service裡面,注了一個dao,與另一個service。
3. 這個testAddUserButAddCountryFail方法內我們做二件事: 首先呼叫dao 來insert 一筆資料,這邊我們會讓它insert 成功。再來用另一個service來呼叫,這個service裡面我們會對交易的方式做測試,都是要讓他fail ,但是要看他對testAddUserButAddCountryFail 產生什麼影響。
看程式比較容易:
UserService裡
@Resource
private UsersDAO userDao;
@Resource
CountryService countryService;
@Transactional(value="deu" )
public void testAddUserButAddCountryFail(){
userDao.addUser();
countryService.addCountry();
}
CountryService 裡
@Resource
private CountryDAO countryDao;
@Transactional(value="deu" )
public void addCountry(){
countryDao.addCountryFail();
}
測試 一 : CountryService中的@Transactional 不做什麼設定,預設就會是串在一起的交易。所以當 countryService.addCountry(); 裡面報錯時,exception丟出來 後,整個交易都會失敗,所以user不會新增成功。
如果我們把 countryService.addCountry(); 包try catch不讓它擴散呢? 不行噢,當程式呼叫 countryService.addCountry();
時,addCountry已被視為是同一個交易,當countryService.addCountry();
裡出問題自動rollback 時,已經馬上影響到addUser了,也就是原來add的user在一出問題時已經被rollback了。當程式回到 testAddUserButAddCountryFail 你想再處理時已經來不及了。
測試二 : 將
@Transactional(value="deu" )
public void addCountry(){
countryDao.addCountryFail();
}
改為
@Transactional(value="deu", propagation=Propagation.REQUIRES_NEW )
public void addCountry(){
countryDao.addCountryFail();
}
我們想要知道,根據Spring的文件來說,這邊會變成一個新的交易,我們要讓它出錯,看影響的狀況如何。我們預期它是一個新的交易,所以它出錯不會影響到新增的User.
結果呢…新增的user還是被rollback了。這一點讓我百思不解。後來研究了網站上的說明,才發現 addCountry雖然是一個新的交易,它自己也rollback了,但是它的exception還是會丟回外部的交易(也就是原來addUser開的那一個),因為我們在
@Transactional(value="deu" )
public void testAddUserButAddCountryFail(){
userDao.addUser();
countryService.addCountry();
}
中沒有做任何處理,這個exception間接導致了 testAddUserButAddCountryFail 也rollback了。
解決方式 : 在 countryService.addCountry(); 加try catch,白話來說就是 : 它是一個獨立的交易,要是裡面出錯了,它的exception不對我( testAddUserButAddCountryFail ) 產生任何影響。
這邊懂了之後,順便講一下Spring的術語:
當程式一進去 testAddUserButAddCountryFail 時,因為我們有
@Transactional ,所以這邊是一個新的交易,叫outer transaction,也就是外面的交易。當我們 countryService.addCountry(); 的時候,因為 addCountry() 裡也是一個交易,這邊叫 inner transaction (內部) 。整個測試案例就是在測試,inner一但有exception時如何對 outer 產生影響。
propagation=Propagation.REQUIRES_NEW會把inner transaction變成一個獨立交易,不寫的話會變成inner的一部份。雖然透過
propagation=Propagation.REQUIRES_NEW變成了一個獨立交易,但是outer還是要對它丟出的例外做處理,都不處理的話,這個例外會影響到原有的交易行為,變成跟著也rollback了。
我這邊順便再貼一下
JB Nizet 的回應,他講的夠清楚了。
–