zoukankan      html  css  js  c++  java
  • Spring 事务

    Spring 事务管理 API 分析

    Spring 框架中,涉及到事务管理的 API 大约有100个左右,其中最重要的有三个:TransactionDefinition、PlatformTransactionManager、TransactionStatus。所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。“给定的事务规则”就是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操作”便是用 PlatformTransactionManager 来表示,而 TransactionStatus 用于表示一个运行着的事务的状态。打一个不恰当的比喻,TransactionDefinition 与 TransactionStatus 的关系就像程序和进程的关系。

    TransactionDefinition

    该接口在前面已经介绍过,它用于定义一个事务。它包含了事务的静态属性,比如:事务传播行为、超时时间等等。Spring 为我们提供了一个默认的实现类:DefaultTransactionDefinition,该类适用于大多数情况。如果该类不能满足需求,可以通过实现 TransactionDefinition 接口来实现自己的事务定义。

    PlatformTransactionManager

    PlatformTransactionManager 用于执行具体的事务操作。接口定义如清单2所示:

    Public interface PlatformTransactionManager{
      TransactionStatus getTransaction(TransactionDefinition definition)
       throws TransactionException;
       void commit(TransactionStatus status)throws TransactionException;
       void rollback(TransactionStatus status)throws TransactionException;
    } 

    根据底层所使用的不同的持久化 API 或框架,PlatformTransactionManager 的主要实现类大致如下:

    • DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操作的情况。
    • HibernateTransactionManager:适用于使用Hibernate进行数据持久化操作的情况。
    • JpaTransactionManager:适用于使用JPA进行数据持久化操作的情况。
    • 另外还有JtaTransactionManager 、JdoTransactionManager、JmsTransactionManager等等。

    如果我们使用JTA进行事务管理,我们可以通过 JNDI 和 Spring 的 JtaTransactionManager 来获取一个容器管理的 DataSource。JtaTransactionManager 不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。而对于其他事务管理器,比如DataSourceTransactionManager,在定义时需要提供底层的数据源作为其属性,也就是 DataSource。与 HibernateTransactionManager 对应的是 SessionFactory,与 JpaTransactionManager 对应的是 EntityManagerFactory 等等。

    TransactionStatus

    PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。返回的TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。TransactionStatus 接口提供了一个简单的控制事务执行和查询事务状态的方法。该接口定义如清单3所示:

    public  interface TransactionStatus{
       boolean isNewTransaction();
       void setRollbackOnly();
       boolean isRollbackOnly();
    }  

    编程式事务管理

    Spring 的编程式事务管理概述

    在 Spring 出现以前,编程式事务管理对基于 POJO 的应用来说是唯一选择。用过 Hibernate 的人都知道,我们需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。通过 Spring 提供的事务管理 API,我们可以在代码中灵活控制事务的执行。在底层,Spring 仍然将事务操作委托给底层的持久化框架来执行。

    基于底层 API 的编程式事务管理

    根据PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口,我们完全可以通过编程的方式来进行事务管理。示例代码如清单4所示:

    public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionDefinition txDefinition;
    private PlatformTransactionManager txManager;
    ......
    public boolean transfer(Long fromId, Long toId, double amount) {
    TransactionStatus txStatus = txManager.getTransaction(txDefinition);
    boolean result = false;
    try {
    result = bankDao.transfer(fromId, toId, amount);
    txManager.commit(txStatus);
    } catch (Exception e) {
    result = false;
    txManager.rollback(txStatus);
    System.out.println("Transfer Error!");
    }
    return result;
    }
    } 

    相应的配置文件如清单5所示:

    <bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    <property name="txManager" ref="transactionManager"/>
    <property name="txDefinition">
    <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
    <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
    </bean>
    </property>
    </bean> 

    如上所示,我们在类中增加了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义一个事务;另一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操作。

    如果方法需要实施事务管理,我们首先需要在方法开始执行前启动一个事务,调用PlatformTransactionManager.getTransaction(...) 方法便可启动一个事务。创建并启动了事务之后,便可以开始编写业务逻辑代码,然后在适当的地方执行事务的提交或者回滚。

    基于 TransactionTemplate 的编程式事务管理

    通过前面的示例可以发现,这种事务管理方式很容易理解,但令人头疼的是,事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。幸好,Spring 也意识到了这些,并提供了简化的方法,这就是 Spring 在数据访问层非常常见的模板回调模式。如清单6所示:

    public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionTemplate transactionTemplate;
    ......
    public boolean transfer(final Long fromId, final Long toId, final double amount) {
    return (Boolean) transactionTemplate.execute(new TransactionCallback(){
    public Object doInTransaction(TransactionStatus status) {
    Object result;
    try {
    result = bankDao.transfer(fromId, toId, amount);
    } catch (Exception e) {
    status.setRollbackOnly();
    result = false;
    System.out.println("Transfer Error!");
    }
    return result;
    }
    });
    }
    } 

    相应的XML配置如下:

    <bean id="bankService"
    class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    <property name="transactionTemplate" ref="transactionTemplate"/>
    </bean> 

    TransactionTemplate 的 execute() 方法有一个 TransactionCallback 类型的参数,该接口中定义了一个 doInTransaction() 方法,通常我们以匿名内部类的方式实现 TransactionCallback 接口,并在其 doInTransaction() 方法中书写业务逻辑代码。这里可以使用默认的事务提交和回滚规则,这样在业务代码中就不需要显式调用任何事务管理的 API。doInTransaction() 方法有一个TransactionStatus 类型的参数,我们可以在方法的任何位置调用该参数的 setRollbackOnly() 方法将事务标识为回滚的,以执行事务回滚。

    根据默认规则,如果在执行回调方法的过程中抛出了未检查异常,或者显式调用了TransacationStatus.setRollbackOnly() 方法,则回滚事务;如果事务执行完成或者抛出了 checked 类型的异常,则提交事务。

    TransactionCallback 接口有一个子接口 TransactionCallbackWithoutResult,该接口中定义了一个 doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 接口主要用于事务过程中不需要返回值的情况。当然,对于不需要返回值的情况,我们仍然可以使用 TransactionCallback 接口,并在方法中返回任意值即可。

    基于 tx 命名空间的声明式事务管理

    Spring 2.x 引入了 <tx> 命名空间,结合使用 <aop> 命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 <aop> 命名空间的切点表达式支持,声明式事务也变得更加强大。

     基于 <tx> 的事务管理示例配置文件 

    <beans......>
    ......
      <bean id="bankService"
        class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
      </bean>
      <tx:advice id="bankAdvice" transaction-manager="transactionManager">
        <tx:attributes>
          <tx:method name="transfer" propagation="REQUIRED"/>
        </tx:attributes>
      </tx:advice>
     
      <aop:config>
        <aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
        <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
      </aop:config>
    ......
    </beans> 

    如果默认的事务属性就能满足要求,那么代码简化为如下所示:

     简化后的基于 <tx> 的事务管理示例配置文件 
    <beans......>
    ......
      <bean id="bankService"
        class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
      </bean>
      <tx:advice id="bankAdvice" transaction-manager="transactionManager" />
      <aop:config>
        <aop:pointcut id="bankPointcut" expression="execution(**.transfer(..))"/>
        <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
      </aop:config>
    ......
    </beans> 

    由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。

    基于 @Transactional 的声明式事务管理

    除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如下所示:

    基于 @Transactional 的事务管理示例配置文件 
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean transfer(Long fromId, Long toId, double amount) {
    return bankDao.transfer(fromId, toId, amount);
    } 

    Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean:

     启用后处理Bean的配置
    <tx:annotation-driven transaction-manager="transactionManager"/> 

    与前面相似,transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。

    虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

    基于 <tx> 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 <tx> 的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于 @Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。另一方面,基于 @Transactional 的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。 

    当我们需要使用BeanPostProcessor时,最直接的使用方法是在Spring配置文件中定义这些Bean。但这些会显得比较笨拙,
    例如:使用@Autowired注解,必须事先在Spring容器中声明

    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>

    使用 @Required注解,就必须声明:

    <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>

    通过标签<context:annotation-config/> ,我们可以同时自动注册这些常用的beanfactory处理器,避免了我们一个个配置的繁琐步骤!

    Spring事务里自定义多线程陷阱

    由于 Spring 的事务管理器是通过线程相关的 ThreadLocal 来保存数据访问基础设施,再结合 IOC 和 AOP 实现高级声明式事务的功能,所以 Spring 的事务天然地和线程有着千丝万缕的联系。

    我们知道 Web 容器本身就是多线程的,Web 容器为一个 Http 请求创建一个独立的线程,所以由此请求所牵涉到的 Spring 容器中的 Bean 也是运行于多线程的环境下。在绝大多数情况下,Spring 的 Bean 都是单实例的(singleton),单实例 Bean 的最大的好处是线程无关性,不存在多线程并发访问的问题,也即是线程安全的。

    一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。我们知道,在传统的编程中,DAO 必须执有一个 Connection,而 Connection 即是状态化的对象。所以传统的 DAO 不能做成单实例的,每次要用时都必须 new 一个新的实例。传统的 Service 由于将有状态的 DAO 作为成员变量,所以传统的 Service 本身也是有状态的。

    但是在 Spring 中,DAO 和 Service 都以单实例的方式存在。Spring 是通过 ThreadLocal 将有状态的变量(如 Connection 等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring 不遗余力地将状态化的对象无状态化,就是要达到单实例化 Bean 的目的。

    由于 Spring 已经通过 ThreadLocal 的设施将 Bean 无状态化,所以 Spring 中单实例 Bean 对线程安全问题拥有了一种天生的免疫能力。不但单实例的 Service 可以成功运行于多线程环境中,Service 本身还可以自由地启动独立线程以执行其它的 Service。下面,通过一个实例对此进行描述:

    @Service("userService")
    public class UserService extends BaseService {
        @Autowired
        private JdbcTemplate jdbcTemplate;
     
        @Autowired
        private ScoreService scoreService;
        //① 在logon方法体中启动一个独立的线程,在该独立的线程中执行ScoreService#addScore()方法
        public void logon(String userName) {
            System.out.println("logon method...");
            updateLastLogonTime(userName);
            Thread myThread = new MyThread(this.scoreService,userName,20);
            myThread.start();
        }
     
        public void updateLastLogonTime(String userName) {
            System.out.println("updateLastLogonTime...");
            String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
        }
        //② 封装ScoreService#addScore()的线程
        private class MyThread extends Thread{
            private ScoreService scoreService;
            private String userName;
            private int toAdd;
            private MyThread(ScoreService scoreService,String userName,int toAdd) {
                this.scoreService = scoreService;
                this.userName = userName;
                this.toAdd = toAdd;
            }
            public void run() {
                scoreService.addScore(userName,toAdd);
            }
        }
    }
     

    将日志级别设置为 DEBUG,执行 UserService#logon() 方法,观察以下输出的日志:

    [main] (AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT ①
     
    [main] (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@1353249] for JDBC transaction
     
    logon method...
     
    updateLastLogonTime...
     
    [main] (JdbcTemplate.java:785) - Executing prepared SQL update
     
    [main] (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?]
     
    [main] (JdbcTemplate.java:794) - SQL update affected 0 rows
     
    [main] (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit
     
    [Thread-2](AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.multithread.ScoreService.addScore]:
        PROPAGATION_REQUIRED,ISOLATION_DEFAULT ②
     
    [main] (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@1353249] ③
     
    [main] (DataSourceTransactionManager.java:323) - Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@1353249] after transaction
     
    [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource
     
    [Thread-2] (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@10dc656] for JDBC transaction
     
    addScore...
     
    [main] (JdbcTemplate.java:416) - Executing SQL statement [DELETE FROM t_user WHERE user_name='tom']
     
    [main] (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource
     
    [Thread-2] (JdbcTemplate.java:785) - Executing prepared SQL update [Thread-2] (JdbcTemplate.java:569) - Executing prepared SQL statement
        [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?]
     
    [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource
     
    [Thread-2] (JdbcTemplate.java:794) - SQL update affected 0 rows
     
    [Thread-2] (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit
     
    [Thread-2] (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@10dc656] ④
     
    [Thread-2] (DataSourceTransactionManager.java:323) - Releasing JDBC Connection[org.apache.commons.dbcp.PoolableConnection@10dc656] after transaction
     

    在 ① 处,在主线程(main)执行的 UserService#logon() 方法的事务启动,在 ③ 处,其对应的事务提交,而在子线程(Thread-2)执行的 ScoreService#addScore() 方法的事务在 ② 处启动,在 ④ 处对应的事务提交。

    所以,我们可以得出这样的结论:在 相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,不同线程下的事务方法工作在独立的事务中。

    public class A {  
        @annotation  
        public B () {  
             
        }  
      
        public C () {  
          B();  
        }    
    }  

    当一个类中,方法B上面有注解(譬如:事务注解),方法B被方法C调用,当其他方法调用方法C的时候方法B上面的注解是无效的!

    参考资料

    全面分析 Spring 的编程式事务管理及声明式事务管理

    Spring事务配置解惑

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

  • 相关阅读:
    Excel文件上传
    SAP 中如何寻找增强
    MySQL性能优化的最佳经验,随时补充
    PHP编程效率的20个要点
    php性能优化
    AngularJS API之$injector ---- 依赖注入
    AngularJS API之extend扩展对象
    AngularJS API之equal比较对象
    AngularJS API之isXXX()
    AngularJS API之toJson 对象转为JSON
  • 原文地址:https://www.cnblogs.com/junzi2099/p/8358215.html
Copyright © 2011-2022 走看看