Spring事務傳播特性例項解析
背景介紹
目前系統正在進行程式碼重構前期預研工作,目標採用spring控制事務以減少開發程式碼量,提高開發效率。同時避免開發人員編碼控制事務所帶來的連結沒有釋放,事務沒有提交,出現異常事務沒有回滾的Bug。
為保證系統能正確使用Spring控制事務,必須很好的理解其傳播特性。在溝通中發現,好多人知道這個概念但是對於事務的實際傳播行為往往模稜兩可。
基於上述原因,本文采用Demo例項的方式對事務的7大傳播特性給出瞭解析。希望能明確大家對事務傳播特性的認識,在以後的工作中成功使用。
Demo說明
採用Junit4.10.0+Spring3.2.1+Spring JDBCTemplate,通過註解方式配置事務,程式碼層次包括主測試類,兩個Service物件,事務在Service開啟。
概念
本地事務
資料庫事務,預設事務為自動提交,因此如果一個業務邏輯類中有多次資料庫操作將無法保證事務的一致性。
Spring事務
對本地事務操作的一次封裝,相當於把使用JDBC程式碼開啟、提交、回滾事務進行了封裝。
上述兩個概念會在demo中用到,以方便大家理解程式碼。
傳播特性
該特性是保證事務是否開啟,業務邏輯是否使用同一個事務的保證。當事務在傳播過程中會受其影響。其傳播特性包括:
1、Propagation.REQUIRED
方法被呼叫時自動開啟事務,在事務範圍內使用則使用同一個事務,否則開啟新事務。
2、Propagation.REQUIRES_NEW
無論何時自身都會開啟事務
3、Propagation.SUPPORTS
自身不會開啟事務,在事務範圍內則使用相同事務,否則不使用事務
4、Propagation.NOT_SUPPORTED
自身不會開啟事務,在事務範圍內使用掛起事務,執行完畢恢復事務
5、Propagation.MANDATORY
自身不開啟事務,必須在事務環境使用否則報錯
6、Propagation.NEVER
自身不會開啟事務,在事務範圍使用丟擲異常
7、Propagation.NESTED
如果一個活動的事務存在,則執行在一個巢狀的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。需要JDBC3.0以上支援。
例項Demo
Propagation.REQUIRED
測試入口程式碼
//會開啟事務,在事務範圍內使用則使用同一個事務,否則開啟新事務
@Test
public void testRequires(){
sService.addStudent();
}
Service程式碼
@Transactional(propagation = Propagation.REQUIRED)
public void addStudent(){
String sql = "insert into student(name) values('st0')";
jdbcTemplate.execute(sql);
tService.addTeacher();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES)
public void addTeacher(){
String sql = "insert into teacher(name) values ('t5')";
jdbcTemplate.execute(sql);
}
經測試無論在tService還是sService如果不丟擲異常,那麼資料提交成功,如果丟擲異常,資料提交失敗。這說明tService和sService使用的是同一個事務,並且只要方法被呼叫就開啟事務。
Propagation.REQUIRES_NEW
測試入口程式碼
//無論何時自身都會開啟事務
@Test
public void testRequiresNew(){
sService.addStudent5();
}
Service程式碼
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addStudent5(){
String sql = "insert into student(name) values('st5')";
jdbcTemplate.execute(sql);
tService.addTeacher5();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addTeacher5(){
String sql = "insert into teacher(name) values ('t5')";
jdbcTemplate.execute(sql);
}
經測試如果在addStudent5中丟擲異常,學生資料不能正確提交,教師資訊被正確提交。說明sService和tService是在兩個獨立的事務中執行,並且只要方法被呼叫就開啟事務。
Propagation.SUPPORTS
測試入口程式碼
//自身不會開啟事務,在事務範圍內則使用相同事務,否則不使用事務
@Test
public void testSupport(){
sService.addStudent6();
}
Service程式碼
@Transactional(propagation = Propagation.SUPPORTS)
public void addStudent6(){
String sql = "insert into student(name) values('st6')";
jdbcTemplate.execute(sql);
tService.addTeacher6();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void addTeacher6(){
String sql = "insert into teacher(name) values ('t6')";
jdbcTemplate.execute(sql);
}
經測試如果在addStudent6中丟擲異常,學生資料和教師資料都被正確提交。說明sService和tService沒有被spring管理和開啟事務,而是使用了本地事務,由於本地事務預設自動提交因此資料都提交成功,但它們使用的卻不是同一個事務,一旦出現異常將導致資料的不一致。
Propagation.NOT_SUPPORTED
測試入口程式碼
//自身不會開啟事務,在事務範圍內使用掛起事務,執行完畢恢復事務
@Test
public void testNotSupport(){
sService.addStudent4();
}
Service程式碼
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addStudent4(){
String sql = "insert into student(name) values('st4')";
jdbcTemplate.execute(sql);
throw new RuntimeException();
}
經測試如果在addStudent4中丟擲異常,學生資料正確提交。說明sService沒有被spring管理和開啟事務,而是使用了本地事務,由於本地事務預設自動提交因此資料都提交成功。
測試入口程式碼
//自身不會開啟事務,在事務範圍內使用掛起事務,執行完畢恢復事務
@Test
public void testNotSupport1(){
sService.addStudent();
}
Service程式碼
@Transactional(propagation = Propagation.REQUIRED)
public void addStudent(){
String sql = "insert into student(name) values('st0')";
jdbcTemplate.execute(sql);
tService.addTeacher4();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addTeacher4(){
String sql = "insert into teacher(name) values ('t4')";
jdbcTemplate.execute(sql);
throw new RuntimeException();
}
經測試如果在addTeacher4中丟擲異常,學生資料提交失敗,教師資料提交成功。說明sService開啟了事務,tService沒有開啟事務,而是使用了本地事務。
Propagation.MANDATORY
測試入口程式碼
//自身不開啟事務,必須在事務環境使用否則報錯
@Test
public void testMandatory(){
sService.addStudent1();
}
Service程式碼
@Transactional(propagation = Propagation.MANDATORY)
public void addStudent1(){
String sql = "insert into student(name) values('st1')";
jdbcTemplate.execute(sql);
}
經測試程式碼報錯。
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory',沒有找到事務環境。
Propagation.NEVER
測試入口程式碼
//自身不會開啟事務,在事務範圍使用丟擲異常
@Test
public void testNever(){
sService.addStudent();
}
Service程式碼
@Transactional(propagation = Propagation.REQUIRED)
public void addStudent(){
String sql = "insert into student(name) values('st0')";
jdbcTemplate.execute(sql);
tService.addTeacher3();
}
@Transactional(propagation = Propagation.NEVER)
public void addTeacher3(){
String sql = "insert into teacher(name) values ('t3')";
jdbcTemplate.execute(sql);
}
經測試程式碼報錯,由於sService開啟了事務,當呼叫sService方法時由於其傳播特性為never,因此報存在事務錯誤。
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
Propagation.NESTED
測試入口程式碼
//如果沒有事務環境其特性同Propagation.REQUIRED,否則巢狀執行事務
@Test
public void testNested(){
sService.addStudent2();
}
Service程式碼
@Transactional(propagation = Propagation.NESTED)
public void addStudent2(){
String sql = "insert into student(name) values('st2')";
jdbcTemplate.execute(sql);
tService.addTeacher2();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NESTED)
public void addTeacher2(){
String sql = "insert into teacher(name) values ('t2')";
jdbcTemplate.execute(sql);
}
經測試程式碼報錯,教師資料和學生資料都沒有提交成功。說明其按照REQUIRED特性執行。對於巢狀事務,大家可以模擬兩個資料來源,一方的失敗不會影響另一方。
以上是所有demo解析。完整的測試程式碼請在:Spring事務傳播特性下載。
另:
大家感興趣SpringAOP入門和原理,可以在SpringAOP下載。