您好,登錄后才能下訂單哦!
學習東西要知行合一,如果只是知道理論而沒實踐過,那么掌握的也不會特別扎實,估計過幾天就會忘記,接下來我們一起實踐來學習Spring事務的傳播屬性。
傳播屬性定義的是當一個事務方法碰到另一個事務方法時的處理行為,一共有七種行為,定義如下
傳播性 | 值 | 描述 |
---|---|---|
PROPAGATION_REQUIRED | 0 | 支持當前事務,如果沒有就新建事務 |
PROPAGATION_SUPPORTS | 1 | 支持當前事務,如果沒有就不以事務的方式運行 |
PROPAGATION_MANDATORY | 2 | 支持當前事務,如果當前沒事務就拋異常 |
PROPAGATION_REQUIRES_NEW | 3 | 無論當前是否有事務,都會新起一個事務 |
PROPAGATION_NOT_SUPPORTED | 4 | 不支持事務,如果當前存在事務,就將此事務掛起不以事務方式運行 |
PROPAGATION_NEVER | 5 | 不支持事務,如果有事務就拋異常 |
PROPAGATION_NESTED | 6 | 如果當前存在事務,在當前事務中再新起一個事務 |
其實只看概念的話已經很直截了當了說明了每個傳播性的作用,此時我們再用具體的例子演示一下每個傳播性屬性下的行為。
此次演示我們使用的是H2數據庫,這個數據庫是作用在內存里面的,所以對于我們演示事務效果來說正好,無需我們在進行其他的配置了,我們新建一個表。將下面語句放在schema.sql
文件里面即可,SpringBoot程序在啟動的時候就會自動為我們在內存里面建立這樣的一個表。
CREATE?TABLE?FOO?(ID?INT?IDENTITY,?BAR?VARCHAR(64));
演示之前我們會定義兩個類FooService
和BarService
。我們使用BarService
里面的方法進行調用FooService
中的方法。
在進行事務演示之前,其實可以分為以下幾種情況,根據排列組合,我們可以得出以下八種情況
調用者:有無事務
調用者:是否有異常
被調用者:有無事務**(這個是通過傳播屬性進行控制的)**所以并不在排列組合中
被調用者:是否有異常
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
有 | 有 | 有 |
有 | 有 | 無 |
有 | 無 | 有 |
有 | 無 | 無 |
無 | 有 | 有 |
無 | 有 | 無 |
無 | 無 | 有 |
無 | 無 | 無 |
其中的RollbackException
是我們自己定義的一個異常類
@Service //?www.1b23.com public?class?BarServiceImpl?implements?BarService{ ????@Autowired ????private?FooService?fooService; ????//?PROPAGATION_REQUIRED演示?無事務 ????@Override ????public?void?testRequiredNoTransactional()?throws?RollbackException?{ ????????fooService.testRequiredTransactional(); ????} }
在BarService
中定義兩個方法,一個是帶著事務的,一個是不帶事務的
//?有事務 @Override @Transactional(rollbackFor?=?Exception.class) public?void?hasTransactional()?throws?RollbackException?{ } //?無事務 @Override public?void?noTransactional()?throws?RollbackException?{??? }
接下來我們就根據俄上面定義的八種情況進行事務傳播屬性的學習。
PROPAGATION_REQUIRED
在此傳播屬性下,被調用方是否新建事務取決去調用者是否帶著事務。
想要了解這個傳播屬性的特性,其實我們演示上面八種情況的兩個例子就夠了
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
第一種情況我們在被調用者拋出異常的情況下,如果查詢不到插入的數據,那么就說明被調用者在調用者沒有事務的情況下自己新建了事務。
第二種情況我們在調用者拋出異常的情況下,如果查詢不到插入的數據,那么就說明被調用者在調用者有事務的情況下就加入當前事務了。
我們先來看一下被調用者的類的方法例子。
@Service //?www.1b23.com public?class?FooServiceImpl?implements?FooService?{ ????@Autowired ????private?JdbcTemplate?jdbcTemplate;???? ????//?REQUIRED傳播屬性-被調用者有異常拋出 ????@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傳播屬性-被調用者無異常拋出 ????@Override ????@Transactional(rollbackFor?=?Exception.class,propagation?=?Propagation.REQUIRED) ????public?void?testRequiredNoException()?throws?RollbackException?{ ????????jdbcTemplate.execute("INSERT?INTO?FOO?(BAR)?VALUES?("+Global.REQUIRED_NO_EXCEPTION+")"); ????} }
接下來我們看一下調用者方法的例子
@Service public?class?BarServiceImpl?implements?BarService{ ????@Autowired ????private?FooService?fooService; ????//?有事務 ????@Override ????@Transactional(rollbackFor?=?Exception.class) ????public?void?hasTransactional()?throws?RollbackException?{ ????????//?調用者有事務,拋異常??被調用者無異常 ????????fooService.testRequiredNoException(); ????????throw?new?RollbackException(); ????} ????//?無事務 ????@Override ????public?void?noTransactional()?throws?RollbackException?{ ????????//?調用者無事務,不拋異常??被調用者有異常 ????????fooService.testRequiredHasException(); ????} }
此時我們在程序調用時進行查詢
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
我們看到我們都沒有查到相應的數據,說明數據都回滾了。此時我們應該就理解了那句話支持當前事務,如果沒有就新建事務。
PROPAGATION_SUPPORTS
被調用者是否有事務,完全依賴于調用者,調用者有事務則有事務,調用者沒事務則沒事務。
接下來我們還是用上面的兩個例子進行演示
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
第一種情況:被調用者拋出異常的情況下,如果仍能查詢到數據,說明事務沒有回滾,說明被調用者沒有事務
第二種情況:調用者拋出異常情況下,如果查不到數據,說明兩個方法在一個事務中
接下來仍然是例子演示
被調用者,只是將@Transactional
注解中的propagation
屬性更換為了Propagation.SUPPORTS
//?SUPPORTS傳播屬性-被調用者有異常拋出 @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傳播屬性-被調用者無異常拋出 @Override @Transactional(rollbackFor?=?Exception.class,propagation?=?Propagation.SUPPORTS) public?void?testSupportsNoException()?throws?RollbackException?{ ????jdbcTemplate.execute("INSERT?INTO?FOO?(BAR)?VALUES?('"+Global.SUPPORTS_NO_EXCEPTION+"')"); }
調用者和上面的例子調用一樣,我們直接查看執行效果
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
我們看到了在第一種情況下查到了數據,說明在第一種情況下被調用者是沒有事務的。此時我們應該就理解了這句話?支持當前事務,如果沒有就不以事務的方式運行。
PROPAGATION_MANDATORY
依然是這兩個例子進行演示
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
第一種情況:因為調用者沒有事務,所以此傳播屬性下應該是拋異常的
第二種情況:被調用者的事務和調用者事務是同樣的
接下來是被調用者的代碼例子
//?MANDATORY傳播屬性-被調用者有異常拋出 @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傳播屬性-被調用者無異常拋出 @Override @Transactional(rollbackFor?=?Exception.class,propagation?=?Propagation.MANDATORY) public?void?testMandatoryNoException()?throws?RollbackException?{ ????jdbcTemplate.execute("INSERT?INTO?FOO?(BAR)?VALUES?('"+Global.SUPPORTS_NO_EXCEPTION+"')"); }
調用者和上面的例子調用一樣,我們直接查看執行效果
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
我們發現和我們推測一樣,說明被調用者是不會自己新建事務的,此時我們應該就理解了這句話支持當前事務,如果當前沒事務就拋異常。
PROPAGATION_REQUIRES_NEW
此傳播屬性下,無論調用者是否有事務,被調用者都會新建一個事務
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
第一種情況:調用者無事務,被調用者會新建事務,所以查不到數據
第二種情況:調用者有事務,被調用者會新建一個事務,所以調用者拋異常影響不到被調用者,所以能查到數據
接下來我們演示代碼。
被調用者
//?REQUIRES_NEW傳播屬性-被調用者有異常拋出 @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傳播屬性-被調用者無異常拋出 @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+"')"); }
調用者的例子和上面的相同,我們直接來看執行情況
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
我們發現和我們的推論是一樣的,說明調用者的事務和被調用者的事務完全無關。此時我們應該就理解這句話了無論當前是否有事務,都會新起一個事務。
PROPAGATION_NOT_SUPPORTED
無論調用者是否有事務,被調用者都不以事務的方法運行
同樣是這兩個例子
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
第一種情況:被調用者都不會有事務,那么在拋異常之后就能查到相應的數據
第二種情況:在調用者有事務的情況下,被調用者也會在無事務環境下運行,所以我們依然能查到數據
接下來驗證我們的猜測
//?NOT_SUPPORTED傳播屬性-被調用者有異常拋出 @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傳播屬性-被調用者無異常拋出 @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+"')"); }
然后查看執行結果
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
我們可以看到在最后兩種情況都查到了數據,根據演示效果應該可以理解這句話了,不支持事務,如果當前存在事務,就將此事務掛起不以事務方式運行。
PROPAGATION_NEVER
調用者有事務,被調用者就會拋出異常
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
這個就不演示,相信大家看到這里應該都會明白在第一種情況下我們是能夠查到數據的。在第二種情況下由于調用者帶著事務,所以會拋異常。
PROPAGATION_NESTED
此傳播屬性下,被調用者的事務是調用者的事務的子集。
我們重點說一下NESTED
的傳播屬性的特性
調用者是否有事務 | 說明 |
---|---|
有 | 被調用者會新起一個事務,此事務和調用者事務是一個嵌套的關系 |
無 | 被調用者會自己新起一個事務 |
關于什么是嵌套事務的關系,我們用下面三個例子能夠進行演示。
調用者是否有事務 | 調用者是否有異常 | 被調用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
有 | 無 | 有 |
第一種情況:如果查不到數據,則說明在調用者無事務情況下,被調用者會新起一個事務
第二種情況:如果查不到數據,說明外層事務能夠影響內層事務
第三種情況:如果查到數據,說明內層事務不影響外層事務
接下來我們編寫具體的代碼
//?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+"')"); }
然后接下來的調用者也會有點區別
@Override @Transactional() public?void?hasTransactionalNoException()?throws?RollbackException?{ ????//?NESTED傳播屬性?-?調用者有事務,不拋異常??被調用者有異常 ????jdbcTemplate.execute("INSERT?INTO?FOO?(BAR)?VALUES?('"+Global.NESTED_HAS_EXCEPTION_TWO+"')"); ????fooService.testNestedHasException(); }
然后執行效果
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
可以看出來嵌套事務的本質就是外層會影響內層,內層不影響外層。而REQUIRES_NEW
則是互不影響。
到現在我們已經全部分析完了七種傳播屬性,從寫這篇文章開始到結束其中也碰到過一些坑,有些是不自己實踐一遍是根本不知道的,所以我還是建議讀者看完這篇文章以后自己進行實踐,演示各種情況,只有這樣才能夠爛熟于心。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。