事務方法嵌套調用的迷茫
Spring事務一個被訛傳很廣說法是:一個事務方法不應該調用另一個事務方法,否則將產生兩個事務.結果造成開發人員在設計事務方法時束手束腳,生怕一不小心就踩到地雷.其實這種是不認識Spring事務傳播機制而造成的誤解,Spring對事務控制的支持統一在TransactionDefinition類中描述,該類有以下幾個重要的介面方法:
◆intgetPropagationBehavior():事務的傳播行為;
◆intgetIsolationLevel():事務的隔離級別;
◆intgetTimeout():事務的過期時間;
◆booleanisReadOnly():事務的讀寫特性.
很明顯,除了事務的傳播行為外,事務的其它特性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#updateLastLogonTime()和ScoreService#addScore()方法,這兩個類都繼承於BaseService.它們之間的類結構說明如下:
圖1.UserService和ScoreService
具體的代碼如下所示:
清單9UserService.java
69.@Service("userService")
70.publicclassUserServiceextendsBaseService{
71.@Autowired
72.privateJdbcTemplatejdbcTemplate;
73.@Autowired
74.privateScoreServicescoreService;
75.
76.publicvoidlogon(StringuserName){
77.updateLastLogonTime(userName);
78.scoreService.addScore(userName,20);
79.}
80.
81.publicvoidupdateLastLogonTime(StringuserName){
82.Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";
83.jdbcTemplate.update(sql,System.currentTimeMillis(),userName);
84.}
85.}
UserService中注入了ScoreService的Bean,ScoreService的代碼如下所示:
清單10ScoreService.java
86.@Service("scoreUserService")
87.publicclassScoreServiceextendsBaseService{
88.@Autowired
89.privateJdbcTemplatejdbcTemplate;
90.publicvoidaddScore(StringuserName,inttoAdd){
91.Stringsql="UPDATEt_useruSETu.score=u.score ?WHEREuser_name=?";
92.jdbcTemplate.update(sql,toAdd,userName);
93.}
94.}
通過Spring的事務配置為ScoreService及UserService中所有公有方法都添加事務增強,讓這些方法都工作於事務環境下.下面是關鍵的配置代碼:
清單11事務增強配置
1.
2.<aop:configproxy-target-classaop:configproxy-target-class="true">
3.<aop:pointcutidaop:pointcutid="serviceJdbcMethod"
4.
5.expression="within(user.nestcall.BaseService )"/>
6.<aop:advisorpointcut-refaop:advisorpointcut-ref="serviceJdbcMethod"
7.advice-ref="jdbcAdvice"order="0"/>
8.</< span>aop:config>
9.<tx:adviceidtx:adviceid="jdbcAdvice"transaction-manager="jdbcManager">
10.<tx:attributes>
11.<tx:methodnametx:methodname="*"/>
12.</< span>tx:attributes>
13.</< span>tx:advice>
將日誌級別設置為DEBUG,啟動Spring容器並執行UserService#logon()的方法,仔細觀察如下的輸出日誌:
清單12執行日誌
14.16:25:04,765DEBUG(AbstractPlatformTransactionManager.java:365)-
15.Creatingnewtransactionwithname[user.nestcall.UserService.logon]:
16.PROPAGATION_REQUIRED,ISOLATION_DEFAULT①為UserService#logon方法啟動一個事務
17.16:25:04,765DEBUG(DataSourceTransactionManager.java:205)-
18.AcquiredConnection[org.apache.commons.dbcp.PoolableConnection@32bd65]
19.forJDBCtransaction
20.logonmethod...
21.updateLastLogonTime...②直接執行updateLastLogonTime方法
22.16:25:04,781DEBUG(JdbcTemplate.java:785)-ExecutingpreparedSQLupdate
23.16:25:04,781DEBUG(JdbcTemplate.java:569)-ExecutingpreparedSQLstatement
24.[UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]
25.16:25:04,828DEBUG(JdbcTemplate.java:794)-SQLupdateaffected0rows
26.16:25:04,828DEBUG(AbstractPlatformTransactionManager.java:470)-Participating
27.inexistingtransaction③ScoreService#addScore方法加入到UserService#logon的事務中
28.addScore...
29.16:25:04,828DEBUG(JdbcTemplate.java:785)-ExecutingpreparedSQLupdate
30.16:25:04,828DEBUG(JdbcTemplate.java:569)-ExecutingpreparedSQLstatement
31.[UPDATEt_useruSETu.score=u.score ?WHEREuser_name=?]
32.16:25:04,828DEBUG(JdbcTemplate.java:794)-SQLupdateaffected0rows
33.16:25:04,828DEBUG(AbstractPlatformTransactionManager.java:752)-
34.Initiatingtransactioncommit
35.16:25:04,828DEBUG(DataSourceTransactionManager.java:265)-CommittingJDBCtransaction
36.onConnection[org.apache.commons.dbcp.PoolableConnection@32bd65]
37.16:25:04,828DEBUG(DataSourceTransactionManager.java:323)-ReleasingJDBCConnection
38.[org.apache.commons.dbcp.PoolableConnection@32bd65]aftertransaction
39.16:25:04,828DEBUG(DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource
從上面的輸入日誌中,可以清楚地看到Spring為UserService#logon()方法啟動了一個新的事務,而UserSerive#updateLastLogonTime()和UserService#logon()是在相同的類中,沒有觀察到有事務傳播行為的發生,其代碼塊好像「直接合併」到UserService#logon()中.接著,當執行到ScoreService#addScore()方法時,我們就觀察到了發生了事務傳播的行為:Participatinginexistingtransaction,這說明ScoreService#addScore()添加到UserService#logon()的事務上下文中,兩者共享同一個事務.最終的結果是UserService的logon(),updateLastLogonTime()以及ScoreService的addScore都工作於同一事務中.
[火星人 ] Spring事務管理高級應用難點剖析(3)已經有570次圍觀