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 事务管理高级应用难点剖析

  • 相关阅读:
    How to alter department in PMS system
    Can't create new folder in windows7
    calculate fraction by oracle
    Long Wei information technology development Limited by Share Ltd interview summary.
    ORACLE BACKUP AND RECOVERY
    DESCRIBE:When you mouse click right-side is open an application and click left-side is attribution.
    ORACLE_TO_CHAR Function
    电脑BOIS设置
    JSP点击表头排序
    jsp+js实现可排序表格
  • 原文地址:https://www.cnblogs.com/junzi2099/p/8358215.html
Copyright © 2011-2022 走看看