`

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 的编程式事务管理及声明式事务管理

    本文将全面分析Spring中的编程式事务管理和声明式事务管理,旨在帮助开发者深入理解这两种事务管理方式,并在实际项目中合理选择。 **编程式事务管理** 编程式事务管理是通过代码直接控制事务的开始、提交、回滚等...

    Spring事务传播Demo.zip

    在"Spring事务传播Demo"中,我们可能看到如何在不同传播行为下,一个事务方法被另一个事务方法调用时,事务是如何管理和传播的。例如,一个PROPAGATION_REQUIRED的方法调用了一个PROPAGATION_REQUIRES_NEW的方法,...

    Spring中事务的传播属性详解

    事务传播行为(Propagation)定义了当一个事务方法被另一个事务方法调用时的行为。在Spring中,这些行为可以通过`TransactionDefinition`接口中的常量来指定。具体来说,`TransactionDefinition`定义了以下七种传播...

    AOP实现自我调用的事物嵌套问题

    当我们遇到"AOP实现自我调用的事物嵌套问题"时,这通常涉及到Spring框架中的事务管理,特别是自调用方法在事务处理时可能会引发的问题。 首先,让我们理解Spring AOP的事务管理是如何工作的。Spring使用代理模式来...

    深入理解Spring声明式事务:源码分析与应用实践

    Spring会创建一个代理对象来包裹被注解的方法,当调用这些方法时,实际上是在调用代理对象,从而实现事务的自动化管理。 在事务管理过程中,Spring通过`@EnableAspectJAutoProxy`注解启用AOP代理。AOP代理可以是JDK...

    spring事务总结.docx

    - **问题分析**:当事务方法被声明为非`public`时,Spring的AOP代理将无法正确识别这些方法,从而导致事务配置无效。 - **示例代码**: ```java @Service public class UserService { @Transactional private...

    spring事物传播测试表

    事务传播行为(Transaction Propagation)是Spring事务管理中一个关键的概念,用于定义在一个事务方法被调用时,如何与当前运行的事务进行交互。本文将深入探讨“Spring事物传播测试表”所涉及的知识点。 首先,...

    Spring事务管理高级应用难点剖析(3)

    } 在这个例子中,`UserService#logon()` 方法是事务方法,它调用了 `UserService#updateLastLogonTime()` 和 `ScoreService#addScore()` 两个方法。由于所有服务类都继承自 `BaseService`,我们可以假设它们都使用...

    spring事务的传播特性.pptx

    在Spring中,事务的传播行为是指当一个事务方法被另一个事务方法调用时,应该怎样处理这两个事务之间的关系。这个概念是通过`@Transactional`注解中的`propagation`属性来设置的。下面将详细讲解Spring事务的七个...

    Spring的事务10种常见失效场景总结.zip

    6. **数据库配置问题**:事务管理依赖于底层数据库支持,如MySQL的InnoDB引擎提供事务支持。如果数据库不支持事务,或者事务隔离级别设置不正确,事务特性将无法正常工作。 7. **懒加载异常**:在事务范围内,如果...

    spring 事务配置

    事务的传播行为定义了当方法调用时如何处理当前事务: - `PROPAGATION_REQUIRED`: 如果当前没有事务,则新建一个事务;如果已经存在一个事务中,则加入到这个事务中。 - `PROPAGATION_SUPPORTS`: 支持当前事务,如果...

    Spring事务详解

    Spring声明式事务主要依赖于AOP(面向切面编程)实现,它通过分析事务注解或XML配置,动态地织入事务管理代码。在声明式事务中,事务属性是非常关键的,它们决定了事务的行为。这些属性包括: 1. 事务的传播行为:...

    spring 事物底层原理分析1

    Spring 提供了事务传播行为,当一个事务方法被另一个事务方法调用时,可以根据事务传播属性来决定如何处理当前事务。常见的传播行为包括: - REQUIRED(默认):如果当前存在事务,则加入当前事务;如果没有事务,...

    spring_transaction_custom:手写弹簧事务管理模块

    Spring的声明式事务管理是基于AOP实现的,通过代理模式在方法调用前后插入事务处理代码。 8. **源码分析**: “spring_transaction_custom-master”项目可能包含了对Spring事务管理器的自定义实现,通过阅读源码...

    通过实际案例摸清楚Spring事务传播的行为.docx

    Spring 事务传播行为是Spring框架中事务管理的重要组成部分,它定义了在多个方法相互调用时,事务如何在这些方法间传播。理解这些行为对于确保应用程序的事务一致性至关重要。接下来,我们将通过具体的案例来深入...

    java事务设计策略

    - **事务传播行为**:定义了当前方法调用时如何与现有的事务相结合。 - **事务隔离级别**:不同的事务隔离级别可以避免脏读、不可重复读等问题,但也可能引入性能上的开销。 - **事务回滚**:当事务执行过程中发生...

    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 联合军种作战的...

Global site tag (gtag.js) - Google Analytics