Spring事務傳播屬性有那么難嗎?看這一篇就夠了
筆者文筆功力尚淺,如有不妥,請慷慨指出,必定感激不盡
學習東西要知行合一,如果只是知道理論而沒實踐過,那么掌握的也不會特別扎實,估計過幾天就會忘記,接下來我們一起實踐來學習Spring事務的傳播屬性。
傳播屬性
傳播屬性定義的是當一個事務方法碰到另一個事務方法時的處理行為,一共有七種行為,定義如下
傳播性 | 值 | 描述 |
---|---|---|
PROPAGATION_REQUIRED |
0 | 支持當前事務,如果沒有就新建事務 |
PROPAGATION_SUPPORTS |
1 | 支持當前事務,如果沒有就不以事務的方式運行 |
PROPAGATION_MANDATORY |
2 | 支持當前事務,如果當前沒事務就拋異常 |
PROPAGATION_REQUIRES_NEW |
3 | 無論當前是否有事務,都會新起一個事務 |
PROPAGATION_NOT_SUPPORTED |
4 | 不支持事務,如果當前存在事務,就將此事務掛起不以事務方式運行 |
PROPAGATION_NEVER |
5 | 不支持事務,如果有事務就拋異常 |
PROPAGATION_NESTED |
6 | 如果當前存在事務,在當前事務中再新起一個事務 |
其實只看概念的話已經(jīng)很直截了當了說明了每個傳播性的作用,此時我們再用具體的例子演示一下每個傳播性屬性下的行為。
此次演示我們使用的是H2數(shù)據(jù)庫,這個數(shù)據(jù)庫是作用在內(nèi)存里面的,所以對于我們演示事務效果來說正好,無需我們在進行其他的配置了,我們新建一個表。將下面語句放在schema.sql文件里面即可,SpringBoot程序在啟動的時候就會自動為我們在內(nèi)存里面建立這樣的一個表。
- CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));
演示之前我們會定義兩個類FooService和BarService。我們使用BarService 里面的方法進行調(diào)用FooService 中的方法。
環(huán)境準備
在進行事務演示之前,其實可以分為以下幾種情況,根據(jù)排列組合,我們可以得出以下八種情況
- 調(diào)用者:有無事務
- 調(diào)用者:是否有異常
- 被調(diào)用者:有無事務**(這個是通過傳播屬性進行控制的)**所以并不在排列組合中
- 被調(diào)用者:是否有異常
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
有 | 有 | 有 |
有 | 有 | 無 |
有 | 無 | 有 |
有 | 無 | 無 |
無 | 有 | 有 |
無 | 有 | 無 |
無 | 無 | 有 |
無 | 無 | 無 |
異常類
其中的RollbackException是我們自己定義的一個異常類
- @Service
- public class BarServiceImpl implements BarService{
- @Autowired
- private FooService fooService;
- // PROPAGATION_REQUIRED演示 無事務
- @Override
- public void testRequiredNoTransactional() throws RollbackException {
- fooService.testRequiredTransactional();
- }
- }
調(diào)用者
在BarService中定義兩個方法,一個是帶著事務的,一個是不帶事務的
- // 有事務
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void hasTransactional() throws RollbackException {
- }
- // 無事務
- @Override
- public void noTransactional() throws RollbackException {
- }
接下來我們就根據(jù)俄上面定義的八種情況進行事務傳播屬性的學習。
PROPAGATION_REQUIRED
在此傳播屬性下,被調(diào)用方是否新建事務取決去調(diào)用者是否帶著事務。
想要了解這個傳播屬性的特性,其實我們演示上面八種情況的兩個例子就夠了
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況我們在被調(diào)用者拋出異常的情況下,如果查詢不到插入的數(shù)據(jù),那么就說明被調(diào)用者在調(diào)用者沒有事務的情況下自己新建了事務。
- 第二種情況我們在調(diào)用者拋出異常的情況下,如果查詢不到插入的數(shù)據(jù),那么就說明被調(diào)用者在調(diào)用者有事務的情況下就加入當前事務了。
我們先來看一下被調(diào)用者的類的方法例子。
- @Service
- public class FooServiceImpl implements FooService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- // REQUIRED傳播屬性-被調(diào)用者有異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
- public void testRequiredHasException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")");
- throw new RollbackException();
- }
- // REQUIRED傳播屬性-被調(diào)用者無異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
- public void testRequiredNoException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")");
- }
- }
接下來我們看一下調(diào)用者方法的例子
- @Service
- public class BarServiceImpl implements BarService{
- @Autowired
- private FooService fooService;
- // 有事務
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void hasTransactional() throws RollbackException {
- // 調(diào)用者有事務,拋異常 被調(diào)用者無異常
- fooService.testRequiredNoException();
- throw new RollbackException();
- }
- // 無事務
- @Override
- public void noTransactional() throws RollbackException {
- // 調(diào)用者無事務,不拋異常 被調(diào)用者有異常
- fooService.testRequiredHasException();
- }
- }
此時我們在程序調(diào)用時進行查詢
- String noException = Global.REQUIRED_NO_EXCEPTION;
- String hasException = Global.REQUIRED_HAS_EXCEPTION;
- try {
- barService.noTransactional();
- }catch (Exception e){
- log.info("第一種情況 {}",
- jdbcTemplate
- .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class));
- }
- try {
- barService.hasTransactional();
- }catch (Exception e){
- log.info("第二種情況 {}",
- jdbcTemplate
- .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class));
- }
查看打印出來的日志
- 2019-10-16 13:02:04.142 INFO 11869 --- [ main] c.e.t.t.TransactionApplication : 第一種情況 0
- 2019-10-16 13:02:04.143 INFO 11869 --- [ main] c.e.t.t.TransactionApplication : 第二種情況 0
我們看到我們都沒有查到相應的數(shù)據(jù),說明數(shù)據(jù)都回滾了。此時我們應該就理解了那句話支持當前事務,如果沒有就新建事務。
PROPAGATION_SUPPORTS
被調(diào)用者是否有事務,完全依賴于調(diào)用者,調(diào)用者有事務則有事務,調(diào)用者沒事務則沒事務。
接下來我們還是用上面的兩個例子進行演示
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:被調(diào)用者拋出異常的情況下,如果仍能查詢到數(shù)據(jù),說明事務沒有回滾,說明被調(diào)用者沒有事務
- 第二種情況:調(diào)用者拋出異常情況下,如果查不到數(shù)據(jù),說明兩個方法在一個事務中
接下來仍然是例子演示
被調(diào)用者,只是將@Transactional 注解中的propagation 屬性更換為了Propagation.SUPPORTS
- // SUPPORTS傳播屬性-被調(diào)用者有異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
- public void testSupportsHasException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
- throw new RollbackException();
- }
- // SUPPORTS傳播屬性-被調(diào)用者無異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
- public void testSupportsNoException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
- }
調(diào)用者和上面的例子調(diào)用一樣,我們直接查看執(zhí)行效果
- 2019-10-16 13:50:27.738 INFO 12174 --- [ main] c.e.t.t.TransactionApplication : 第一種情況 1
- 2019-10-16 13:50:27.741 INFO 12174 --- [ main] c.e.t.t.TransactionApplication : 第二種情況 0
我們看到了在第一種情況下查到了數(shù)據(jù),說明在第一種情況下被調(diào)用者是沒有事務的。此時我們應該就理解了這句話 支持當前事務,如果沒有就不以事務的方式運行。
PROPAGATION_MANDATORY
依然是這兩個例子進行演示
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:因為調(diào)用者沒有事務,所以此傳播屬性下應該是拋異常的
- 第二種情況:被調(diào)用者的事務和調(diào)用者事務是同樣的
接下來是被調(diào)用者的代碼例子
- // MANDATORY傳播屬性-被調(diào)用者有異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
- public void testMandatoryHasException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')");
- throw new RollbackException();
- }
- // MANDATORY傳播屬性-被調(diào)用者無異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
- public void testMandatoryNoException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')");
- }
調(diào)用者和上面的例子調(diào)用一樣,我們直接查看執(zhí)行效果
- 2019-10-16 13:58:39.178 ERROR 12317 --- [ main] c.e.t.t.TransactionApplication : org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
- 2019-10-16 13:58:39.276 INFO 12317 --- [ main] c.e.t.t.TransactionApplication : 第一種情況 0
- 2019-10-16 13:58:39.281 INFO 12317 --- [ main] c.e.t.t.TransactionApplication : 第二種情況 0
我們發(fā)現(xiàn)和我們推測一樣,說明被調(diào)用者是不會自己新建事務的,此時我們應該就理解了這句話支持當前事務,如果當前沒事務就拋異常。
PROPAGATION_REQUIRES_NEW
此傳播屬性下,無論調(diào)用者是否有事務,被調(diào)用者都會新建一個事務
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:調(diào)用者無事務,被調(diào)用者會新建事務,所以查不到數(shù)據(jù)
- 第二種情況:調(diào)用者有事務,被調(diào)用者會新建一個事務,所以調(diào)用者拋異常影響不到被調(diào)用者,所以能查到數(shù)據(jù)
接下來我們演示代碼。
被調(diào)用者
- // REQUIRES_NEW傳播屬性-被調(diào)用者有異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
- public void testRequiresNewHasException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_HAS_EXCEPTION+"')");
- throw new RollbackException();
- }
- // REQUIRES_NEW傳播屬性-被調(diào)用者無異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
- public void testRequiresNewNoException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_NO_EXCEPTION+"')");
- }
調(diào)用者的例子和上面的相同,我們直接來看執(zhí)行情況
- 2019-10-16 16:29:20.296 INFO 15553 --- [ main] c.e.t.t.TransactionApplication : 第一種情況 0
- 2019-10-16 16:29:20.298 INFO 15553 --- [ main] c.e.t.t.TransactionApplication : 第二種情況 1
我們發(fā)現(xiàn)和我們的推論是一樣的,說明調(diào)用者的事務和被調(diào)用者的事務完全無關。此時我們應該就理解這句話了無論當前是否有事務,都會新起一個事務。
PROPAGATION_NOT_SUPPORTED
無論調(diào)用者是否有事務,被調(diào)用者都不以事務的方法運行
同樣是這兩個例子
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:被調(diào)用者都不會有事務,那么在拋異常之后就能查到相應的數(shù)據(jù)
- 第二種情況:在調(diào)用者有事務的情況下,被調(diào)用者也會在無事務環(huán)境下運行,所以我們依然能查到數(shù)據(jù)
接下來驗證我們的猜測
- // NOT_SUPPORTED傳播屬性-被調(diào)用者有異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
- public void testNotSupportHasException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_HAS_EXCEPTION+"')");
- throw new RollbackException();
- }
- // NOT_SUPPORTED傳播屬性-被調(diào)用者無異常拋出
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
- public void testNotSupportNoException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_NO_EXCEPTION+"')");
- }
然后查看執(zhí)行結(jié)果
- 2019-10-16 16:38:35.065 INFO 15739 --- [ main] c.e.t.t.TransactionApplication : 第一種情況 1
- 2019-10-16 16:38:35.067 INFO 15739 --- [ main] c.e.t.t.TransactionApplication : 第二種情況 1
我們可以看到在最后兩種情況都查到了數(shù)據(jù),根據(jù)演示效果應該可以理解這句話了,不支持事務,如果當前存在事務,就將此事務掛起不以事務方式運行。
PROPAGATION_NEVER
調(diào)用者有事務,被調(diào)用者就會拋出異常
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
這個就不演示,相信大家看到這里應該都會明白在第一種情況下我們是能夠查到數(shù)據(jù)的。在第二種情況下由于調(diào)用者帶著事務,所以會拋異常。
PROPAGATION_NESTED
此傳播屬性下,被調(diào)用者的事務是調(diào)用者的事務的子集。
我們重點說一下NESTED的傳播屬性的特性
調(diào)用者是否有事務 | 說明 |
---|---|
有 | 被調(diào)用者會新起一個事務,此事務和調(diào)用者事務是一個嵌套的關系 |
無 | 被調(diào)用者會自己新起一個事務 |
關于什么是嵌套事務的關系,我們用下面三個例子能夠進行演示。
調(diào)用者是否有事務 | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
有 | 無 | 有 |
- 第一種情況:如果查不到數(shù)據(jù),則說明在調(diào)用者無事務情況下,被調(diào)用者會新起一個事務
- 第二種情況:如果查不到數(shù)據(jù),說明外層事務能夠影響內(nèi)層事務
- 第三種情況:如果查到數(shù)據(jù),說明內(nèi)層事務不影響外層事務
接下來我們編寫具體的代碼
- // NESTED傳播屬性-回滾事務
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
- public void testNestedHasException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION+"')");
- // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- throw new RollbackException();
- }
- // NESTED傳播屬性-不回滾事務
- @Override
- @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
- public void testNestedNoException() throws RollbackException {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')");
- }
然后接下來的調(diào)用者也會有點區(qū)別
- @Override
- @Transactional()
- public void hasTransactionalNoException() throws RollbackException {
- // NESTED傳播屬性 - 調(diào)用者有事務,不拋異常 被調(diào)用者有異常
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')");
- fooService.testNestedHasException();
- }
然后執(zhí)行效果
- 2019-10-16 18:01:06.387 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : 第一種情況 0
- 2019-10-16 18:01:06.389 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : 第二種情況 0
- 2019-10-16 18:01:06.390 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : 第三種情況 1
可以看出來嵌套事務的本質(zhì)就是外層會影響內(nèi)層,內(nèi)層不影響外層。而REQUIRES_NEW則是互不影響。
總結(jié)
到現(xiàn)在我們已經(jīng)全部分析完了七種傳播屬性,從寫這篇文章開始到結(jié)束其中也碰到過一些坑,有些是不自己實踐一遍是根本不知道的,所以我還是建議讀者看完這篇文章以后自己進行實踐,演示各種情況,只有這樣才能夠爛熟于心。