具體上次寫事務管理這塊內容已經過去好幾個月了,昨天打了個草稿,這次學習事務管理相當於是複習,也相當於是鞏固這個知識點。關於事務和緩存,在Spring中都有專門的管理機制,當下的開發趨勢中,關於Annotation的表達方式越來越常用,之前的事務管理文章中所舉例是採用了配置文件的方式,這次就採用註解的方式來鞏固下Spring事務管理的傳播行爲吧。
事務管理:https://blog.csdn.net/Nerver_77/article/details/79876040
Spring中的事務管理:https://blog.csdn.net/Nerver_77/article/details/79896351
以上兩篇是以往所總結的內容,這篇就不贅述,直接開始正題:事務管理中的傳播行爲!
當事務方法被另一個事務方法所調用時,須指定事務應該如何傳播。
這裏將搭建一個測試事務傳播行爲的測試用例,在此之前,先搭建所需要的環境。
1. 建立測試數據表:test
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;
2. Dao層搭建:Mybatis-plus代碼生成器
關於Mybatis-plus的代碼生成器詳情可見文檔:http://mp.baomidou.com/#/generate-code
類比Mybatis的逆向工程,都是用來生成Dao層代碼的,Mybatis-plus基於的BaseMapper,實現了基本的CURD操作的封裝,我們只需要生成代碼即可,無需更改。當然如果有其他複雜查詢需要自定義。
針對test表,生成對應的pojo對象:Test.java,Dao接口:TestMapper.java,mapping文件:TestMapper.xml
生成的Dao接口,繼承了BaseMapper,基本的CURD都有的,這裏測試用例無需複雜查詢,這裏就不寫了。
public interface TestMapper extends BaseMapper<Test> {
}
3. Service層搭建:接口 + 實現類方式
接口:這樣相同的接口設立了三個,改個名字就好。
public interface TestService1 {
void test1();
}
實現類: Test1的實現類如下,Test2、Test3的實現類一樣。
@Service
public class TestServiceImpl1 implements TestService1{
@Autowired
private TestMapper testMapper;
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void test1() {
Test test1 = new Test();
test1.setValue("test1事務測試");
testMapper.insert(test1);
try {
testService2.test2();
} catch (Exception e) {
System.err.println("test2有異常");
e.printStackTrace();
}
testService3.test3();
}
}
@Service
public class TestServiceImpl2 implements TestService2{
@Autowired
private TestMapper testMapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void test2() {
Test test2 = new Test();
test2.setValue("test2事務測試");
testMapper.insert(test2);
// int i = 1/0;
}
}
這裏需要說明下:實現類中的方法都是調用testMapper進行數據添加,test1方法中嵌套了test2、test3的數據添加業務。牽扯到事務的傳播行爲,必須要存在事務方法的嵌套,我們在下述進行傳播行爲測試的時候,先採用test1和test2。
採用Junit創建測試類:test1爲測試入口。
public class TransactionTest extends BaseJunit {
@Autowired
private TestService1 testService1;
@Test
public void test(){
testService1.test1();
}
}
4. 開啓測試:下面會根據事務的每個傳播行爲進行測試。
提示:自制異常:1/0 /by zero ,事務掛起:當前事務中的方法也是處於執行狀態 , pass:控制檯結果正確,這裏不重複上圖。
方法開啓事務:採用 @Transactional註解方式聲明。@Transactional(propagation = Propagation.REQUIRED)
詳細的註解聲明問題在第一個測試中進行詳細介紹,後面的測試大同小異,就是更改propagation參數而已。
以下傳播行爲的測試默認都採用test1、test2,只有PROPAGATION_NESTED時候會使用test3。
PROPAGATION_REQUIRED--表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啓動一個新的事務
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.REQUIRED
測試:對test1而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1內部,會正常執行。
數據庫結果:test1事務測試 + test2事務測試(ID字段自增長,無需在意)
控制檯結果:pass
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.REQUIRED + 自制異常
測試:對test1而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1內部,但是test2中存在異常,基於事務1的一致性,事務1會進行回滾。
數據庫結果:無記錄
控制檯結果:出現異常,事務1中的方法執行結果回滾。
PROPAGATION_SUPPORTS--表示當前方法不需要事務,但是如果存在當前事務的話,那麼該方法會在這個事務中運行,如果當前沒有事務,就以非事務方式執行。
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.SUPPORTS
測試:對test1而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1內部,會正常執行。
數據庫結果:test1事務測試 + test2事務測試
控制檯結果:pass
上述情況即:test1、test2都在事務1中執行,任何一方法出現異常就會導致事務回滾。
test1:未開啓事務
test2:propagation = Propagation.SUPPORTS + 自制異常
測試:對test2而言,當前方法沒有運行事務中,因此test2將以非事務方式執行(遇到異常也不會進行回滾)。
數據庫結果:test1事務測試 + test2事務測試
控制檯結果:test1未處於事務中,正常執行,test2中聲明瞭SUPPORTS並自制異常,非事務方式執行方法,出現異常也不會回滾。
PROPAGATION_MANDATORY--表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常。
test1:未開啓事務
test2:propagation = Propagation.MANDATORY
測試:對test2而言沒有處於事務中,會拋出異常。
數據庫結果:test1事務測試
控制檯結果:MANDATORY所聲明的事務中爲發現事務存在,即外圍無事務存在。 當test2外圍存在事務時,就會事務執行成功。
PROPAGATION_REQUIRES_NEW--表示當前方法必須運行在它自己的事務中。一個新的事務將被啓動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.REQUIRED_NEW + 自制異常
測試:對test1而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1內部,但是test2會重新開啓一個新事務2,此時事務1和事務2互不影響。
數據庫結果:test1事務測試
控制檯結果:test1正常 test2回滾
PROPAGATION_NOT_SUPPORTED--表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.REQUIRED + 自制異常
測試:對test1而言,當前方法運行在事務1中。對test2而言,當前所處與事務1中,但是test2不應該運行在事務中,也就是test2獨立出事務1,以非事務方式運行,此時事務1掛起(不影響方法執行),。
數據庫結果:test1事務測試 + test2事務測試
控制檯結果:test2獨立出事務以非事務方式運行。出現異常也不會回滾。
PROPAGATION_NEVER--表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.NEVER
測試:對test1而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1中,存在事務運行,拋出異常。
數據庫結果:test1事務測試
控制檯結果:NEVER 表明發現外圍存在事務運行,拋出異常。
test1:未開啓事務
test2:propagation = Propagation.NEVER
測試:對test2而言不存在事務運行,可以正常執行。
數據庫結果:test1事務測試 + test2事務測試
控制檯結果:pass
PROPAGATION_NESTED--一個事務內部嵌套事務的執行不會影響外部事務,但外部事務的執行要影響內部
一個事務內部嵌套事務的執行不會影響外部事務:REQUIRES_NEW,開啓全新事務,獨立外圍事務運行。
外部事務的執行要影響內部:REQUIRED,外圍事務影響內部方法。
需要把這兩者需求結合起來:
1.外部事物影響內部事務
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.NESTED
test3:propagation = Propagation.REQUIRED + 自制異常
測試:對test1、test3而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1內部,並聲明傳播方式爲NESTED,外部事物出現異常,此時事務1會回滾。
數據庫結果:無記錄
控制檯結果:test3出現異常,由於外部事物影響內部,所以test1、test2、test3方法均回滾。
2.內部事務不影響外部事物
test1:propagation = Propagation.REQUIRED
test2:propagation = Propagation.NESTED + 自制異常
test3:propagation = Propagation.REQUIRED
測試:對test1、test3而言,當前方法運行在事務1中,對test2而言當前事務運行在事務1內部,並聲明傳播方式爲NESTED,test2構建的內部事務出現異常,不影響外部事務。
數據庫結果:test1事務測試 + test3事務測試
控制檯結果:內部事務不影響外部事物,外部事務1正常執行,內部事務出現異常,方法觸發回滾。
至此,關於Spring事務管理的傳播行爲的測試完結,瞭解事務傳播行爲可以合理安排業務執行。基於註解方式聲明事務這一操作,實質上基於Spring AOP的動態代理機制,相對底層的學習會在以後展開,需要學習的小夥伴可以參考之前兩篇文章對概念性進行理解,結合測試用例,有利於理解和學習。