0028-多個事務互相呼叫
阿新 • • 發佈:2019-02-05
spring的事務傳播行為就是多個事務方法相互呼叫時,事務如何在這些方法間傳播。
spring事務一個被訛傳很廣說法是:一個事務方法不應該呼叫另一個事務方法,否則將產生兩個事務。結果造成開發人員在設計事務方法時束手束腳,生怕一不小心就踩到地雷。
其實這是不認識Spring事務傳播機制而造成的誤解,Spring對事務控制的支援統一在TransactionDefinition類中描述,該類有以下幾個重要的介面方法:
int getPropagationBehavior():事務的傳播行為
int getIsolationLevel():事務的隔離級別
int getTimeout():事務的過期時間
boolean isReadOnly():事務的讀寫特性
很明顯,除了事務的傳播行為外,事務的其他特性Spring是藉助底層資源的功能來完成的,Spring無非只充當個代理的角色。但是事務的傳播行為卻是 Spring憑藉自身的框架提供的功能,是Spring提供給開發者最珍貴的禮物,訛傳的說法玷汙了Spring事務框架最美麗的光環。
所謂事務傳播行為就是多個事務方法相互呼叫時,事務如何在這些方法間傳播。Spring支援以下7種事務傳播行為。
PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,就加入到這個事務中。這是最常見的選擇。
PROPAGATION_SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY:使用當前的事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
PROPAGATION_NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。
Spring預設的事務傳播行為是PROPAGATION_REQUIRED,它適合絕大多數的情況,如果多個ServiveX#methodX()均工 作在事務環境下(即均被Spring事務增強),且程式中存在如下的呼叫 鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這3個 服務類的3個方法通過Spring的事務傳播機制都工作在同一個事務中。
相互巢狀的服務方法
我們來看一下例項,UserService#logon()方法內部呼叫了UserService#updateLastLogon Time()和ScoreService#addScore()方法,這兩個類都繼承於BaseService。它們之間的類結構如下圖所示:
UserService#logon()方法內部呼叫了ScoreService#addScore()的方法,兩者都分別通過Spring AOP進行了事務增強,則它們工作於同一事務中。來看具體的程式碼:
01 <SPAN style="FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound="true">package com.baobaotao.nestcall;
02 …
03 @Service("userService")
04 public class UserService extends BaseService {
05 @Autowired
06 private JdbcTemplate jdbcTemplate;
07
08 @Autowired
09 private ScoreService scoreService;
10
11 //①該方法巢狀呼叫了本類的其他方法及其他服務類的方法
12 public void logon(String userName) {
13 System.out.println("before userService.updateLastLogonTime...");
14 updateLastLogonTime(userName);//①-1本服務類的其他方法
15 System.out.println("after userService.updateLastLogonTime...");
16
17 System.out.println("before scoreService.addScore...");
18 scoreService.addScore(userName, 20); //①-2其他服務類的其他方法
19 System.out.println("after scoreService.addScore...");
20
21 }
22 public void updateLastLogonTime(String userName) {
23 String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
24 jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
25 }</SPAN>
UserService中注入了ScoreService的Bean,而ScoreService的程式碼如下所示:
01 <SPAN style="FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound="true">package com.baobaotao.nestcall;
02 …
03 @Service("scoreUserService")
04 public class ScoreService extends BaseService{
05
06 @Autowired
07 private JdbcTemplate jdbcTemplate;
08
09 public void addScore(String userName, int toAdd) {
10 String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
11 jdbcTemplate.update(sql, toAdd, userName);
12 }
13 }</SPAN>
通過Spring配置為ScoreService及UserService中所有公有方法都新增Spring AOP的事務增強,讓UserService的logon()和updateLastLogonTime()及ScoreService的 addScore()方法都工作於事務環境下。下面是關鍵的配置程式碼:
01 <SPAN style="FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound="true"><?xml version="1.0" encoding="UTF-8" ?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04 xmlns:context="http://www.springframework.org/schema/context"
05 xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
06 xmlns:tx="http://www.springframework.org/schema/tx"
07 xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
08 http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
09 <context:component-scan base-package="com.baobaotao.nestcall"/>
10 …
11 <bean id="jdbcManager"
12 class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
13 p:dataSource-ref="dataSource"/>
14
15 <!--①通過以下配置為所有繼承BaseService類的所有子類的所有public方法都新增事務增強-->
16 <aop:config proxy-target-class="true">
17 <aop:pointcut id="serviceJdbcMethod"
18 expression="within(com.baobaotao.nestcall.BaseService+)"/>
19 <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/>
20 </aop:config>
21 <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
22 <tx:attributes>
23 <tx:method name="*"/>
24 </tx:attributes>
25 </tx:advice>
26 </beans></SPAN>
將日誌級別設定為DEBUG,啟動Spring容器並執行UserService#logon()的方法,仔細觀察如下輸出日誌:
引用
before userService.logon method...
//①建立了一個事務
Creating new transaction with name [com.baobaotao.nestcall.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, [email protected] , MySQL-AB JDBC Driver] for JDBC transaction
Switching JDBC Connection [jdbc:mysql://localhost:3306/sampledb, [email protected] , MySQL-AB JDBC Driver] to manual commit
before userService.updateLastLogonTime...
<!--②updateLastLogonTime()和logon()在同一個Bean中,並未發生加入已存在事務上下文的
動作,而是“天然”地工作於相同的事務上下文-->
Executing prepared SQL update
Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]
SQL update affected 1 rows
after userService.updateLastLogonTime...
before scoreService.addScore...
//③ScoreService#addScore方法加入到①處啟動的事務上下文中
Participating in existing transaction
Executing prepared SQL update
Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]
SQL update affected 1 rows
after scoreService.addScore...
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, [email protected] , MySQL-AB JDBC Driver]
…
after userService.logon method...
從上面的輸出日誌中,可以清楚地看到Spring為UserService#logon()方法啟動了一個新的事務,而 UserSerive#updateLastLogonTime()和UserService#logon()是在相同的類中,沒有觀察到有事務傳播行為 的發生,其程式碼塊好像“直接合並”到UserService#logon()中。
然而在執行到ScoreService#addScore()方法時,我們就觀察到發生一個事務傳播的行為:" Participating in existing transaction ",這說明ScoreService#addScore()新增到UserService#logon()的事務上下文中,兩者共享同一個事務。所以最終 的結果是UserService的logon()、updateLastLogonTime()以及ScoreService的addScore都工作於 同一事務中。
spring事務一個被訛傳很廣說法是:一個事務方法不應該呼叫另一個事務方法,否則將產生兩個事務。結果造成開發人員在設計事務方法時束手束腳,生怕一不小心就踩到地雷。
其實這是不認識Spring事務傳播機制而造成的誤解,Spring對事務控制的支援統一在TransactionDefinition類中描述,該類有以下幾個重要的介面方法:
int getPropagationBehavior():事務的傳播行為
int getIsolationLevel():事務的隔離級別
int getTimeout():事務的過期時間
boolean isReadOnly():事務的讀寫特性
很明顯,除了事務的傳播行為外,事務的其他特性Spring是藉助底層資源的功能來完成的,Spring無非只充當個代理的角色。但是事務的傳播行為卻是 Spring憑藉自身的框架提供的功能,是Spring提供給開發者最珍貴的禮物,訛傳的說法玷汙了Spring事務框架最美麗的光環。
所謂事務傳播行為就是多個事務方法相互呼叫時,事務如何在這些方法間傳播。Spring支援以下7種事務傳播行為。
PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,就加入到這個事務中。這是最常見的選擇。
PROPAGATION_SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY:使用當前的事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
PROPAGATION_NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。
Spring預設的事務傳播行為是PROPAGATION_REQUIRED,它適合絕大多數的情況,如果多個ServiveX#methodX()均工 作在事務環境下(即均被Spring事務增強),且程式中存在如下的呼叫 鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這3個 服務類的3個方法通過Spring的事務傳播機制都工作在同一個事務中。
相互巢狀的服務方法
我們來看一下例項,UserService#logon()方法內部呼叫了UserService#updateLastLogon Time()和ScoreService#addScore()方法,這兩個類都繼承於BaseService。它們之間的類結構如下圖所示:
UserService#logon()方法內部呼叫了ScoreService#addScore()的方法,兩者都分別通過Spring AOP進行了事務增強,則它們工作於同一事務中。來看具體的程式碼:
01 <SPAN style="FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound="true">package com.baobaotao.nestcall;
02 …
03 @Service("userService")
04 public class UserService extends BaseService {
05 @Autowired
06 private JdbcTemplate jdbcTemplate;
07
08 @Autowired
09 private ScoreService scoreService;
10
11 //①該方法巢狀呼叫了本類的其他方法及其他服務類的方法
12 public void logon(String userName) {
13 System.out.println("before userService.updateLastLogonTime...");
14 updateLastLogonTime(userName);//①-1本服務類的其他方法
15 System.out.println("after userService.updateLastLogonTime...");
16
17 System.out.println("before scoreService.addScore...");
18 scoreService.addScore(userName, 20); //①-2其他服務類的其他方法
19 System.out.println("after scoreService.addScore...");
20
21 }
22 public void updateLastLogonTime(String userName) {
23 String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
24 jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
25 }</SPAN>
UserService中注入了ScoreService的Bean,而ScoreService的程式碼如下所示:
01 <SPAN style="FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound="true">package com.baobaotao.nestcall;
02 …
03 @Service("scoreUserService")
04 public class ScoreService extends BaseService{
05
06 @Autowired
07 private JdbcTemplate jdbcTemplate;
08
09 public void addScore(String userName, int toAdd) {
10 String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
11 jdbcTemplate.update(sql, toAdd, userName);
12 }
13 }</SPAN>
通過Spring配置為ScoreService及UserService中所有公有方法都新增Spring AOP的事務增強,讓UserService的logon()和updateLastLogonTime()及ScoreService的 addScore()方法都工作於事務環境下。下面是關鍵的配置程式碼:
01 <SPAN style="FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound="true"><?xml version="1.0" encoding="UTF-8" ?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04 xmlns:context="http://www.springframework.org/schema/context"
05 xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
06 xmlns:tx="http://www.springframework.org/schema/tx"
07 xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
08 http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
09 <context:component-scan base-package="com.baobaotao.nestcall"/>
10 …
11 <bean id="jdbcManager"
12 class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
13 p:dataSource-ref="dataSource"/>
14
15 <!--①通過以下配置為所有繼承BaseService類的所有子類的所有public方法都新增事務增強-->
16 <aop:config proxy-target-class="true">
17 <aop:pointcut id="serviceJdbcMethod"
18 expression="within(com.baobaotao.nestcall.BaseService+)"/>
19 <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/>
20 </aop:config>
21 <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
22 <tx:attributes>
23 <tx:method name="*"/>
24 </tx:attributes>
25 </tx:advice>
26 </beans></SPAN>
將日誌級別設定為DEBUG,啟動Spring容器並執行UserService#logon()的方法,仔細觀察如下輸出日誌:
引用
before userService.logon method...
//①建立了一個事務
Creating new transaction with name [com.baobaotao.nestcall.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb,
Switching JDBC Connection [jdbc:mysql://localhost:3306/sampledb, [email protected] , MySQL-AB JDBC Driver] to manual commit
before userService.updateLastLogonTime...
<!--②updateLastLogonTime()和logon()在同一個Bean中,並未發生加入已存在事務上下文的
動作,而是“天然”地工作於相同的事務上下文-->
Executing prepared SQL update
Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]
SQL update affected 1 rows
after userService.updateLastLogonTime...
before scoreService.addScore...
//③ScoreService#addScore方法加入到①處啟動的事務上下文中
Participating in existing transaction
Executing prepared SQL update
Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]
SQL update affected 1 rows
after scoreService.addScore...
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb,
…
after userService.logon method...
從上面的輸出日誌中,可以清楚地看到Spring為UserService#logon()方法啟動了一個新的事務,而 UserSerive#updateLastLogonTime()和UserService#logon()是在相同的類中,沒有觀察到有事務傳播行為 的發生,其程式碼塊好像“直接合並”到UserService#logon()中。
然而在執行到ScoreService#addScore()方法時,我們就觀察到發生一個事務傳播的行為:" Participating in existing transaction ",這說明ScoreService#addScore()新增到UserService#logon()的事務上下文中,兩者共享同一個事務。所以最終 的結果是UserService的logon()、updateLastLogonTime()以及ScoreService的addScore都工作於 同一事務中。