Spring的事务管理难点剖析(3):事务方法嵌套调用的迷茫

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进行了事务加强,则它们工做于同一事务中。来看具体的代码: java

package com.baobaotao.nestcall; … @Service("userService") public class UserService extends BaseService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private ScoreService scoreService; //①该方法嵌套调用了本类的其余方法及其余服务类的方法 public void logon(String userName) { System.out.println("before userService.updateLastLogonTime..."); updateLastLogonTime(userName);//①-1本服务类的其余方法 System.out.println("after userService.updateLastLogonTime..."); System.out.println("before scoreService.addScore..."); scoreService.addScore(userName, 20); //①-2其余服务类的其余方法 System.out.println("after scoreService.addScore..."); } 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的代码以下所示: mysql

package com.baobaotao.nestcall; … @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中全部公有方法都添加Spring AOP的事务加强,让UserService的logon()和updateLastLogonTime()及ScoreService的 addScore()方法都工做于事务环境下。下面是关键的配置代码: spring

<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <context:component-scan base-package="com.baobaotao.nestcall"/> … <bean id="jdbcManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/> <!--①经过如下配置为全部继承BaseService类的全部子类的全部public方法都添加事务加强--> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" expression="within(com.baobaotao.nestcall.BaseService+)"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans>

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

引用
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, UserName=root@localhost , MySQL-AB JDBC Driver] for JDBC transaction
Switching JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost , 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, UserName=root@localhost , 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 3.x企业应用开发实战》
相关文章
相关标签/搜索