读书人

spring事宜之事物方法嵌套调用的迷茫

发布时间: 2012-11-08 08:48:11 作者: rapoo

spring事务之事物方法嵌套调用的迷茫

转自:http://bbs.paris8.org/viewthread.php?tid=4079

?

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#updateLastLogonTime() 和 ScoreService#addScore() 方法,这两个类都继承于 BaseService。它们之间的类结构说明如下:


图 1. UserService 和 ScoreService
spring事宜之事物方法嵌套调用的迷茫?

具体的代码如下所示:


清单 9 UserService.java
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???
@Service("userService")
public class UserService extends BaseService {
? ? @Autowired
? ? private JdbcTemplate jdbcTemplate;
? ? @Autowired
? ? private ScoreService scoreService;

? ? public void logon(String userName) {
? ?? ???updateLastLogonTime(userName);
? ?? ???scoreService.addScore(userName, 20);
? ? }

? ? public void updateLastLogonTime(String userName) {
? ?? ???String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
? ?? ???jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
? ? }
}



UserService 中注入了 ScoreService 的 Bean,ScoreService 的代码如下所示:


清单 10 ScoreService.java
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???
@Service("scoreUserService")
public class ScoreService extends BaseService{
? ? @Autowired
? ? private JdbcTemplate jdbcTemplate;
? ? public void addScore(String userName, int toAdd) {
? ?? ???String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
? ?? ???jdbcTemplate.update(sql, toAdd, userName);
? ? }
}



通过 Spring 的事务配置为 ScoreService 及 UserService 中所有公有方法都添加事务增强,让这些方法都工作于事务环境下。下面是关键的配置代码:


清单 11 事务增强配置
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???
<!-- 添加Spring事务增强 -->
<aop:config proxy-target-order="0"/>
</aop:config>
<tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
? ? <tx:attributes>
? ?? ???<tx:method name="*"/>
? ? </tx:attributes>
</tx:advice>



将日志级别设置为 DEBUG,启动 Spring 容器并执行 UserService#logon() 的方法,仔细观察如下的输出日志:


清单 12 执行日志
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???
16:25:04,765 DEBUG (AbstractPlatformTransactionManager.java:365) -?
? ? Creating new transaction with name [user.nestcall.UserService.logon]:?
? ?? ???PROPAGATION_REQUIRED,ISOLATION_DEFAULT??①为UserService#logon方法启动一个事务

16:25:04,765 DEBUG (DataSourceTransactionManager.java:205) -?
? ? Acquired Connection [org.apache.commons.dbcp.PoolableConnection@32bd65]?
? ?? ???for JDBC transaction

logon method...

updateLastLogonTime... ②直接执行updateLastLogonTime方法

16:25:04,781 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update

16:25:04,781 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement
? ? [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]

16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows

16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:470) - Participating
? ? in existing transaction? ?③ScoreService#addScore方法加入到UserService#logon的事务中

addScore...

16:25:04,828 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update

16:25:04,828 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement
? ? [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]

16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows

16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:752) -?
? ? Initiating transaction commit

④提交事务

16:25:04,828 DEBUG (DataSourceTransactionManager.java:265) - Committing JDBC transaction
? ? on Connection [org.apache.commons.dbcp.PoolableConnection@32bd65]

16:25:04,828 DEBUG (DataSourceTransactionManager.java:323) - Releasing JDBC Connection?
? ? [org.apache.commons.dbcp.PoolableConnection@32bd65] after transaction

16:25:04,828 DEBUG (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource



从上面的输入日志中,可以清楚地看到 Spring 为 UserService#logon() 方法启动了一个新的事务,而 UserSerive#updateLastLogonTime() 和 UserService#logon() 是在相同的类中,没有观察到有事务传播行为的发生,其代码块好像“直接合并”到 UserService#logon() 中。接着,当执行到 ScoreService#addScore() 方法时,我们就观察到了发生了事务传播的行为:Participating in existing transaction,这说明 ScoreService#addScore() 添加到 UserService#logon() 的事务上下文中,两者共享同一个事务。所以最终的结果是 UserService 的 logon(), updateLastLogonTime() 以及 ScoreService 的 addScore 都工作于同一事务中。

读书人网 >编程

热点推荐