zoukankan      html  css  js  c++  java
  • Spring事务传播特性的浅析——事务方法嵌套调用的迷茫

    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 3.x企业应用开发实战》 

  • 相关阅读:
    ACM ICPC 2008–2009 NEERC MSC A, B, C, G, L
    POJ 1088 滑雪 DP
    UVA 11584 最短回文串划分 DP
    POJ 2531 Network Saboteur DFS+剪枝
    UVa 10739 String to Palindrome 字符串dp
    UVa 11151 Longest Palindrome 字符串dp
    UVa 10154 Weights and Measures dp 降维
    UVa 10271 Chopsticks dp
    UVa 10617 Again Palindrome 字符串dp
    UVa 10651 Pebble Solitaire 状态压缩 dp
  • 原文地址:https://www.cnblogs.com/lgjava/p/9439239.html
Copyright © 2011-2022 走看看