`

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进行了事务增强,则它们工作于同一事务中。来看具体的代码:
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的代码如下所示:

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()方法都工作于事务环境下。下面是关键的配置代码:
<?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()的方法,仔细观察如下输出日志:
引用
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 4.x企业应用开发实战》
  
  • 大小: 10.5 KB
分享到:
评论
12 楼 jinnianshilongnian 2012-06-11  
jinnianshilongnian 写道
janwen 写道
janwen 写道
有些log4j日志我看到怎么跟博客里的输出不一样啊.是什么配置不对吗?
[DEBUG]	2012-06-11 13:36:49,417	[main]	transaction.interceptor.NameMatchTransactionAttributeSource	(NameMatchTransactionAttributeSource.java:94)	-Adding transactional method [*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
[DEBUG]	2012-06-11 13:37:08,490	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:08,495	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [INSERT INTO stuff SET name=?,age=?]
[DEBUG]	2012-06-11 13:37:08,536	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows
[DEBUG]	2012-06-11 13:37:20,050	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:20,052	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [UPDATE stuff sf SET sf.name=?,sf.age=? WHERE sf.name=?]
[DEBUG]	2012-06-11 13:37:20,062	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows



版本一样嘛? 结果如果正确 这个不用纠结


晕 还以为我博客那 哈哈哈
11 楼 jinnianshilongnian 2012-06-11  
janwen 写道
janwen 写道
有些log4j日志我看到怎么跟博客里的输出不一样啊.是什么配置不对吗?
[DEBUG]	2012-06-11 13:36:49,417	[main]	transaction.interceptor.NameMatchTransactionAttributeSource	(NameMatchTransactionAttributeSource.java:94)	-Adding transactional method [*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
[DEBUG]	2012-06-11 13:37:08,490	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:08,495	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [INSERT INTO stuff SET name=?,age=?]
[DEBUG]	2012-06-11 13:37:08,536	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows
[DEBUG]	2012-06-11 13:37:20,050	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:20,052	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [UPDATE stuff sf SET sf.name=?,sf.age=? WHERE sf.name=?]
[DEBUG]	2012-06-11 13:37:20,062	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows



版本一样嘛? 结果如果正确 这个不用纠结
10 楼 janwen 2012-06-11  
janwen 写道
有些log4j日志我看到怎么跟博客里的输出不一样啊.是什么配置不对吗?
[DEBUG]	2012-06-11 13:36:49,417	[main]	transaction.interceptor.NameMatchTransactionAttributeSource	(NameMatchTransactionAttributeSource.java:94)	-Adding transactional method [*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
[DEBUG]	2012-06-11 13:37:08,490	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:08,495	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [INSERT INTO stuff SET name=?,age=?]
[DEBUG]	2012-06-11 13:37:08,536	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows
[DEBUG]	2012-06-11 13:37:20,050	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:20,052	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [UPDATE stuff sf SET sf.name=?,sf.age=? WHERE sf.name=?]
[DEBUG]	2012-06-11 13:37:20,062	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows


9 楼 janwen 2012-06-11  
有些log4j日志我看到怎么跟博客里的输出不一样啊.是什么配置不对吗?
[DEBUG]	2012-06-11 13:36:49,417	[main]	transaction.interceptor.NameMatchTransactionAttributeSource	(NameMatchTransactionAttributeSource.java:94)	-Adding transactional method [*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
[DEBUG]	2012-06-11 13:37:08,490	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:08,495	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [INSERT INTO stuff SET name=?,age=?]
[DEBUG]	2012-06-11 13:37:08,536	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows
[DEBUG]	2012-06-11 13:37:20,050	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:811)	-Executing prepared SQL update
[DEBUG]	2012-06-11 13:37:20,052	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:570)	-Executing prepared SQL statement [UPDATE stuff sf SET sf.name=?,sf.age=? WHERE sf.name=?]
[DEBUG]	2012-06-11 13:37:20,062	[main]	jdbc.core.JdbcTemplate	(JdbcTemplate.java:820)	-SQL update affected 1 rows

8 楼 jinnianshilongnian 2012-04-15  
huang_yong 写道
补充:
如何在代码中使用@Transactional注解及其属性,最好也能说明一下。

可以参考 我的这篇帖子 http://www.iteye.com/topic/1121357
7 楼 huang_yong 2012-04-14  
补充:
如何在代码中使用@Transactional注解及其属性,最好也能说明一下。
6 楼 huang_yong 2012-04-14  
建议作者,关于其它传播机制也需要补充一下吧。

关于“隔离级别”也是否需要补充一下呢?

谢谢!
5 楼 jinnianshilongnian 2012-03-10  
OracleX 写道
jinnianshilongnian 写道
OracleX 写道
正常情况下没有问题,如果其中一个事务出现异常,另一个正常执行需要怎么处理


你的需求是 RequiresNew

为什么是RequiresNew呢?明明是嵌套事务


具体 可以看我画的几个传播行为的图,地址:
http://jinnianshilongnian.iteye.com/blog/1441271
4 楼 jinnianshilongnian 2012-03-10  
OracleX 写道
jinnianshilongnian 写道
OracleX 写道
正常情况下没有问题,如果其中一个事务出现异常,另一个正常执行需要怎么处理


你的需求是 RequiresNew

为什么是RequiresNew呢?明明是嵌套事务


c
  a
  b

当b失败时a成功,对不?
    因此RequiresNew也是可以的。

当然也可以用Nested传播行为实现。

如图


3 楼 OracleX 2012-03-10  
jinnianshilongnian 写道
OracleX 写道
正常情况下没有问题,如果其中一个事务出现异常,另一个正常执行需要怎么处理


你的需求是 RequiresNew

为什么是RequiresNew呢?明明是嵌套事务
2 楼 jinnianshilongnian 2012-03-09  
OracleX 写道
正常情况下没有问题,如果其中一个事务出现异常,另一个正常执行需要怎么处理


你的需求是 RequiresNew
1 楼 OracleX 2012-03-09  
正常情况下没有问题,如果其中一个事务出现异常,另一个正常执行需要怎么处理

相关推荐

    Spring.3.x企业应用开发实战(完整版).part2

    10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套的服务方法 10.4 多线程的困惑 10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的...

    Spring3.x企业应用开发实战(完整版) part1

    10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套的服务方法 10.4 多线程的困惑 10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的...

    asp.net知识库

    动态调用对象的属性和方法——性能和灵活性兼备的方法 消除由try/catch语句带来的warning 微软的应试题完整版(附答案) 一个时间转换的问题,顺便谈谈搜索技巧 .net中的正则表达式使用高级技巧 (一) C#静态成员和...

    JAVA上百实例源码以及开源项目

    6个目标文件,EJB来模拟银行ATM机的流程及操作:获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用...

    JAVA上百实例源码以及开源项目源代码

    6个目标文件,EJB来模拟银行ATM机的流程及操作:获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用...

    javaSE代码实例

    6.6.1 调用不存在的方法或成员变量 87 6.6.2 用空引用进行调用 88 6.6.3 数组的空引用问题 89 6.7 局部变量 89 6.7.1 局部变量的作用域 89 6.7.2 局部变量的初始化 90 6.8 this预定义对象引用 92 6.9...

    领域驱动设计与模式实战

    6.4.5 分离单元测试和数据库调用测试 6.5 查询 6.5.1 单组查询对象 6.5.2 单组查询对象的代价 6.5.3 将查询定位到哪里 6.5.4 再次将聚合作为工具 6.5.5 将规格用于查询 6.5.6 其他查询选择 6.6 小结 第7章 应用规则...

    软件加密技术内幕配套光盘(iso版本)

    arbin翻译的《CRC原理及其逆向分析方法》 │ └─codeEncrypt ;5.8 代码与数据结合技术 │ ├─codeEncrypt ;代码与数据结合的实例 │ └─tools ;Encrypter.exe工具及其源码 │ ├─Chap06...........................

Global site tag (gtag.js) - Google Analytics