Spring事務(wù)傳播屬性有那么難嗎?看這一篇就夠了
筆者文筆功力尚淺,如有不妥,請(qǐng)慷慨指出,必定感激不盡
學(xué)習(xí)東西要知行合一,如果只是知道理論而沒實(shí)踐過,那么掌握的也不會(huì)特別扎實(shí),估計(jì)過幾天就會(huì)忘記,接下來我們一起實(shí)踐來學(xué)習(xí)Spring事務(wù)的傳播屬性。
傳播屬性
傳播屬性定義的是當(dāng)一個(gè)事務(wù)方法碰到另一個(gè)事務(wù)方法時(shí)的處理行為,一共有七種行為,定義如下
傳播性 | 值 | 描述 |
---|---|---|
PROPAGATION_REQUIRED |
0 | 支持當(dāng)前事務(wù),如果沒有就新建事務(wù) |
PROPAGATION_SUPPORTS |
1 | 支持當(dāng)前事務(wù),如果沒有就不以事務(wù)的方式運(yùn)行 |
PROPAGATION_MANDATORY |
2 | 支持當(dāng)前事務(wù),如果當(dāng)前沒事務(wù)就拋異常 |
PROPAGATION_REQUIRES_NEW |
3 | 無論當(dāng)前是否有事務(wù),都會(huì)新起一個(gè)事務(wù) |
PROPAGATION_NOT_SUPPORTED |
4 | 不支持事務(wù),如果當(dāng)前存在事務(wù),就將此事務(wù)掛起不以事務(wù)方式運(yùn)行 |
PROPAGATION_NEVER |
5 | 不支持事務(wù),如果有事務(wù)就拋異常 |
PROPAGATION_NESTED |
6 | 如果當(dāng)前存在事務(wù),在當(dāng)前事務(wù)中再新起一個(gè)事務(wù) |
其實(shí)只看概念的話已經(jīng)很直截了當(dāng)了說明了每個(gè)傳播性的作用,此時(shí)我們?cè)儆镁唧w的例子演示一下每個(gè)傳播性屬性下的行為。
此次演示我們使用的是H2數(shù)據(jù)庫,這個(gè)數(shù)據(jù)庫是作用在內(nèi)存里面的,所以對(duì)于我們演示事務(wù)效果來說正好,無需我們?cè)谶M(jìn)行其他的配置了,我們新建一個(gè)表。將下面語句放在schema.sql文件里面即可,SpringBoot程序在啟動(dòng)的時(shí)候就會(huì)自動(dòng)為我們?cè)趦?nèi)存里面建立這樣的一個(gè)表。
- CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));
演示之前我們會(huì)定義兩個(gè)類FooService和BarService。我們使用BarService 里面的方法進(jìn)行調(diào)用FooService 中的方法。
環(huán)境準(zhǔn)備
在進(jìn)行事務(wù)演示之前,其實(shí)可以分為以下幾種情況,根據(jù)排列組合,我們可以得出以下八種情況
- 調(diào)用者:有無事務(wù)
- 調(diào)用者:是否有異常
- 被調(diào)用者:有無事務(wù)**(這個(gè)是通過傳播屬性進(jìn)行控制的)**所以并不在排列組合中
- 被調(diào)用者:是否有異常
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
有 | 有 | 有 |
有 | 有 | 無 |
有 | 無 | 有 |
有 | 無 | 無 |
無 | 有 | 有 |
無 | 有 | 無 |
無 | 無 | 有 |
無 | 無 | 無 |
異常類
其中的RollbackException是我們自己定義的一個(gè)異常類
- @Service
- public class BarServiceImpl implements BarService{
- @Autowired
- private FooService fooService;
- // PROPAGATION_REQUIRED演示 無事務(wù)
- @Override
- public void testRequiredNoTransactional() throws RollbackException {
- fooService.testRequiredTransactional();
- }
- }
調(diào)用者
在BarService中定義兩個(gè)方法,一個(gè)是帶著事務(wù)的,一個(gè)是不帶事務(wù)的
- // 有事務(wù)
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void hasTransactional() throws RollbackException {
- }
- // 無事務(wù)
- @Override
- public void noTransactional() throws RollbackException {
- }
接下來我們就根據(jù)俄上面定義的八種情況進(jìn)行事務(wù)傳播屬性的學(xué)習(xí)。
PROPAGATION_REQUIRED
在此傳播屬性下,被調(diào)用方是否新建事務(wù)取決去調(diào)用者是否帶著事務(wù)。
想要了解這個(gè)傳播屬性的特性,其實(shí)我們演示上面八種情況的兩個(gè)例子就夠了
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況我們?cè)诒徽{(diào)用者拋出異常的情況下,如果查詢不到插入的數(shù)據(jù),那么就說明被調(diào)用者在調(diào)用者沒有事務(wù)的情況下自己新建了事務(wù)。
- 第二種情況我們?cè)谡{(diào)用者拋出異常的情況下,如果查詢不到插入的數(shù)據(jù),那么就說明被調(diào)用者在調(diào)用者有事務(wù)的情況下就加入當(dāng)前事務(wù)了。
我們先來看一下被調(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;
- // 有事務(wù)
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void hasTransactional() throws RollbackException {
- // 調(diào)用者有事務(wù),拋異常 被調(diào)用者無異常
- fooService.testRequiredNoException();
- throw new RollbackException();
- }
- // 無事務(wù)
- @Override
- public void noTransactional() throws RollbackException {
- // 調(diào)用者無事務(wù),不拋異常 被調(diào)用者有異常
- fooService.testRequiredHasException();
- }
- }
此時(shí)我們?cè)诔绦蛘{(diào)用時(shí)進(jìn)行查詢
- 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
我們看到我們都沒有查到相應(yīng)的數(shù)據(jù),說明數(shù)據(jù)都回滾了。此時(shí)我們應(yīng)該就理解了那句話支持當(dāng)前事務(wù),如果沒有就新建事務(wù)。
PROPAGATION_SUPPORTS
被調(diào)用者是否有事務(wù),完全依賴于調(diào)用者,調(diào)用者有事務(wù)則有事務(wù),調(diào)用者沒事務(wù)則沒事務(wù)。
接下來我們還是用上面的兩個(gè)例子進(jìn)行演示
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:被調(diào)用者拋出異常的情況下,如果仍能查詢到數(shù)據(jù),說明事務(wù)沒有回滾,說明被調(diào)用者沒有事務(wù)
- 第二種情況:調(diào)用者拋出異常情況下,如果查不到數(shù)據(jù),說明兩個(gè)方法在一個(gè)事務(wù)中
接下來仍然是例子演示
被調(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)用者是沒有事務(wù)的。此時(shí)我們應(yīng)該就理解了這句話 支持當(dāng)前事務(wù),如果沒有就不以事務(wù)的方式運(yùn)行。
PROPAGATION_MANDATORY
依然是這兩個(gè)例子進(jìn)行演示
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:因?yàn)檎{(diào)用者沒有事務(wù),所以此傳播屬性下應(yīng)該是拋異常的
- 第二種情況:被調(diào)用者的事務(wù)和調(diào)用者事務(wù)是同樣的
接下來是被調(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)和我們推測(cè)一樣,說明被調(diào)用者是不會(huì)自己新建事務(wù)的,此時(shí)我們應(yīng)該就理解了這句話支持當(dāng)前事務(wù),如果當(dāng)前沒事務(wù)就拋異常。
PROPAGATION_REQUIRES_NEW
此傳播屬性下,無論調(diào)用者是否有事務(wù),被調(diào)用者都會(huì)新建一個(gè)事務(wù)
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:調(diào)用者無事務(wù),被調(diào)用者會(huì)新建事務(wù),所以查不到數(shù)據(jù)
- 第二種情況:調(diào)用者有事務(wù),被調(diào)用者會(huì)新建一個(gè)事務(wù),所以調(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)用者的事務(wù)和被調(diào)用者的事務(wù)完全無關(guān)。此時(shí)我們應(yīng)該就理解這句話了無論當(dāng)前是否有事務(wù),都會(huì)新起一個(gè)事務(wù)。
PROPAGATION_NOT_SUPPORTED
無論調(diào)用者是否有事務(wù),被調(diào)用者都不以事務(wù)的方法運(yùn)行
同樣是這兩個(gè)例子
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
- 第一種情況:被調(diào)用者都不會(huì)有事務(wù),那么在拋異常之后就能查到相應(yīng)的數(shù)據(jù)
- 第二種情況:在調(diào)用者有事務(wù)的情況下,被調(diào)用者也會(huì)在無事務(wù)環(huán)境下運(yùn)行,所以我們依然能查到數(shù)據(jù)
接下來驗(yàn)證我們的猜測(cè)
- // 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ù)演示效果應(yīng)該可以理解這句話了,不支持事務(wù),如果當(dāng)前存在事務(wù),就將此事務(wù)掛起不以事務(wù)方式運(yùn)行。
PROPAGATION_NEVER
調(diào)用者有事務(wù),被調(diào)用者就會(huì)拋出異常
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
這個(gè)就不演示,相信大家看到這里應(yīng)該都會(huì)明白在第一種情況下我們是能夠查到數(shù)據(jù)的。在第二種情況下由于調(diào)用者帶著事務(wù),所以會(huì)拋異常。
PROPAGATION_NESTED
此傳播屬性下,被調(diào)用者的事務(wù)是調(diào)用者的事務(wù)的子集。
我們重點(diǎn)說一下NESTED的傳播屬性的特性
調(diào)用者是否有事務(wù) | 說明 |
---|---|
有 | 被調(diào)用者會(huì)新起一個(gè)事務(wù),此事務(wù)和調(diào)用者事務(wù)是一個(gè)嵌套的關(guān)系 |
無 | 被調(diào)用者會(huì)自己新起一個(gè)事務(wù) |
關(guān)于什么是嵌套事務(wù)的關(guān)系,我們用下面三個(gè)例子能夠進(jìn)行演示。
調(diào)用者是否有事務(wù) | 調(diào)用者是否有異常 | 被調(diào)用者是否有異常 |
---|---|---|
無 | 無 | 有 |
有 | 有 | 無 |
有 | 無 | 有 |
- 第一種情況:如果查不到數(shù)據(jù),則說明在調(diào)用者無事務(wù)情況下,被調(diào)用者會(huì)新起一個(gè)事務(wù)
- 第二種情況:如果查不到數(shù)據(jù),說明外層事務(wù)能夠影響內(nèi)層事務(wù)
- 第三種情況:如果查到數(shù)據(jù),說明內(nèi)層事務(wù)不影響外層事務(wù)
接下來我們編寫具體的代碼
- // NESTED傳播屬性-回滾事務(wù)
- @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傳播屬性-不回滾事務(wù)
- @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)用者也會(huì)有點(diǎn)區(qū)別
- @Override
- @Transactional()
- public void hasTransactionalNoException() throws RollbackException {
- // NESTED傳播屬性 - 調(diào)用者有事務(wù),不拋異常 被調(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
可以看出來嵌套事務(wù)的本質(zhì)就是外層會(huì)影響內(nèi)層,內(nèi)層不影響外層。而REQUIRES_NEW則是互不影響。
總結(jié)
到現(xiàn)在我們已經(jīng)全部分析完了七種傳播屬性,從寫這篇文章開始到結(jié)束其中也碰到過一些坑,有些是不自己實(shí)踐一遍是根本不知道的,所以我還是建議讀者看完這篇文章以后自己進(jìn)行實(shí)踐,演示各種情況,只有這樣才能夠爛熟于心。