事务方法嵌套调用的迷茫
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.
具体的代码如下所示:
清单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都工作于同一事务中.