spring的事务

Spring框架其中的一个优点是它全面的事务支持,其好处有:

  1. 为复杂的事务API提供了一致的编程模型,如JTA、JDBC、Hibernate、JPA和JDO
  2. 支持声明式事务管理
  3. 更易于使用的编程式事务管理API
  4. 很好的整合Spring的各种数据访问抽象

     传统来说,J2EE有两个事务有两种选择: 全局和本地 .
     全局事务由应用服务器管理,使用JTA。
            其主要限制在于, 通常需要将JTA、JNDI同时使用,因为通常JTA的UserTransaction是通过JNDI获得的。
     局部事务和资源相关的,比如和一个JDBC链接关联的事务。
            其限制在于它们不能同事用于多个事务性资源。例如,使用JDBC连接事务管理的代码不能用于全局的JTA事务中。另外一个缺点是 局部事务趋向于入侵式编程模型。

       Spring解决了这方面的问题,它让开发者能够使用在 任何环境下使用一致的编程模型。它同时提供声明式和编程式事务管理。 事务管理是多数使用者的首选,推荐使用!

局部事务的定义

     通常,我们需要先定义一个DataSource,然后使用Spring的DataSourceTransactionManager,并传入指向DataSource的引用:

<bean id= "dataSource" class = "org.apache.commons.dbcp.BasicDataSource"
         destroy-method= "close" >
         <property name= "driverClassName" value= "${jdbc.driverClassName}" />
         <property name= "url" value= "${jdbc.url}" />
         <property name= "username" value= "${jdbc.username}" />
         <property name= "password" value= "${jdbc.password}" />
     </bean>

    再定义一个 事务管理 类:

<!-- 事务管理 -->
     <bean id= "txManager"
class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
         <property name= "dataSource" ref= "dataSource" />
     </bean>

Spring解决了这方面的问题,它让开发者能够使用在 任何环境下使用一致的编程模型。它同时提供声明式和编程式事务管理。 事务管理是多数使用者的首选,推荐使用!

     Spring事务抽象的关键是 事务策略的概念 ,
     主要通过org.springframework.transaction.PlatformTransactionManager接口定义

  
这里需要说一下 TransactionStatus对象,它代表一个新的或已经存在的事务.

  

另外,TransactionDefinition接口指定:

  • 事务隔离: 当前事务和其他事务的隔离的程度。 事务能否看到其他事务未提交的写数据?
  • 事务传播: 通常在一个事务中执行的所有代码都会在这个事务中运行。但是,如果一个事务上下文已经存在,有几个选项可以指定一个事务性的执行行为;例如;挂起现有事务,创建一个新的事务。
  • 事务超时: 事务在超时前能运行多久? (自动被底层事务基础设施回滚)
  • 只读状态: 只读事务不修改任何数据,只读事务在某些情况下,是非常有用的优化。

下面,做个解释一下,上面的几个特点:

事务隔离级别

  隔离级别是指若干并发事务之间的隔离程度。

  TransactionDefinition接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:
       默认值,使用底层数据库隔离级别。大部分数据库来说 这通常是
       TransactionDefinition.ISOLATION_READ_COMMITED
  • TransactionDefinition.ISOLATIOIN_READ_UNCOMMITTED:
        该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。其不能防止脏读和不可重复读,
         较少使用
  • TransactionDefinition.ISOLATION_READ_COMMITTED:
         该隔离级别表示一个事务只能读取另一个事务已经提交的数据。其可以防止脏读,推荐使用
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:
         该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的数据也会被忽略。 该级别防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:
         所有事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

         所谓事务传播行为是指, 如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

         TransactionDefinition定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:
         如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个事务。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:
         创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:
         如果当前存在事务,则加入该事务;如果没有当前事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
         以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:
         以非事务方式运行,如果当前存在事务,则抛出异常
  • TransactionDefinition.PROPAGATION_MANDATORY:
        如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:
        如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED.

     上面的前6项,都是从EJB引入的。 他们共享相同的概念。 而PROPAGATION_NESTED是Spring 所特有的。以PROPAGATION_NESTED启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。
     其类似于JDBC的SavePoint的概念,嵌套事务的子事务就是保存点的一个应用,一个事务中包括多个保存点,每一个嵌套子事务。 另外,外部事务的回滚也导致嵌套事务的回滚。

事务超时

     所谓事务超时,指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在TransactionDefinition中以int的值来表示超时时间,单位是秒。 

事务的只读属性

     事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。 所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在TransactionDefinition中以boolean类型来表示改事务是否只读。 

Spring事务管理API分析

    在Spring 框架中事务管理的API最重要的有三个: TransactionDefinition、PlatformTransactionManager、TransactionStatus. 事务管理可以理解为: "按照给定的事务规则来执行提交或者回滚操作"!
    "给定的事务规则"就是用TransactionDefinition表示的, "按照......来执行或回滚操作"便是用PlatformTransactionManager来表示,而TransactionStatus 用于表示一个运行着的事务的状态。打一个不恰当的比喻,TransactionDefinition与TransactionStatus的关系就像是程序和进程的关系. 

编程式事务管理

     编程式事务管理我们需要在代码中显式的调用beginTransaction()、commit() 、rollback()等事务管理相关的方法,这就是编程式事务管理。 通过Spring 提供的事务管理API,我们可以在代码中灵活的控制事务执行。而在底层,Spring仍然将事务操作委托给底层的持久化框架来执行。

     小例子:

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);
            }
                return result;
        }
}


 配置文件:

<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 类型的属性,用于执行事务管理操作。

基于TransactionTemplate的编程式事务管理

    上面的方式很容易理解,但是有一个问题,就是事务处理的代码散落在各个类中,破坏了原有代码的条理性,我们可以在Spring使用模板回调模式

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


配置文件也有相应的修改:

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

声明式事务管理

    Spring的声明式事务管理在底层是建立在AOP的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行目标完成之后根据执行情况提交或者回滚事务。

   声明式事务最大的优点在于 不需要通过编程的方式管理事务,这样就不需要在业务逻辑中参杂事务管理代码,只需要在配置文件中做相关的事务规则声明,便可将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正式AOP的用武之地。

   声明式事务的大致流程如下图: 

下面介绍几种常用的声明式事务策略: 

基于TransactionInterceptor的声明式事务管理

<!-- 定义事务规则 -->
<bean id= "transactionInterceptor"
    class = "org.springframework.transaction.interceptor.TransactionInterceptor" >
     <!-- 指定事务管理器 -->
     <property name= "transactionManager" ref= "transactionManager" />
     <!-- 定义事务规则 -->
     <property name= "transactionAttributes" >
         <props>
             <!-- key 是方法名 ,值为事务属性-->
             <prop key= "transfer" >PROPAGATION_REQUIRED</prop>
         </props>
     </property>
</bean>
 
<!-- 组装target和advice -->
<bean id= "bankService" class = "org.springframework.aop.framework.ProxyFactoryBean" >
     <property name= "target" ref= "bankService" />
     <property name= "interceptorNames" >
         <list>
             <idref bean= "transactionInterceptor" />
         </list>
     </property>
</bean>


    这里需要说明一下指定事务属性的取值规则:

    传播行为[.隔离级别][. 只读属性][.超时属性][不影响提交的异常][.导致回滚的异常]

  • 传播行为是唯一必须设置的属性,其他都可以忽略,Spring 为我们提供了合理的默认值.
  • 传播行为的取值必须以"PROPAGATION_"开头,具体包括 PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、 PROPAGATION_REQUIRED、ROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值
  • 隔离级别的取值必须以"ISOLATION_"开头,具体包括ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
  • 如果事务是只读的,那么我们可以指定只读属性,使用readOnly指定,否则我们不需要设置该属性
  • 超时属性的取值必须以"TIMEOUT_"开头,后面跟一个int类型的值,表示超时时间,单位是秒。
  • 不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务仍然正常提交。必须在每一个异常的名字前面加上"+"。异常的名字可以是类名的一部分。比如"+RuntimeException"、"+tion"等等
  • 导致回滚的异常是指,当前事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上"-"。异常的名字可以是类名的全部或者部分,比如"-RuntimeException"、"-tion"等等.

    举两个例子:

&nbsp;<property name= "*Service" >
     PROPAGATION_REQUIRED. ISOLATION_READ_COMMITTED. TIMEOUT_20.
        +AbcException. +DefException. -HijException
</property>

    上面的表达式主要针对方法名为Service结尾的方法,使用PROPAGATION_REQUIRED事务传播行为,事务的隔离级别是ISOLATION_READ_COMMITTED, 超时时间为20秒,当事务抛出AbcException或者DefException类型的异常,则仍然提交,当抛出HijException类型的异常时,必须回滚事务。这里没有指定readOnly,表示事务不是只读的。


基于TransactionProxyFactoryBean的事务管理

       上面的配置有个局限,即配置文件太多. 我们必须给每一个目标对象配置一个ProxyFactoryBean,加上目标对象本身,每一个业务类可能需要对应三个<bean/>配置,随着业务类的增多,配置文件会变得越来越庞大,所以Spring提供了TransactionProxyFacotyBean,用于将TransactionInterceptor和ProxyFactoryBean的配置合二为一。

&nbsp;    <!-- transactionProxyFacotryBean方式 -->
     <bean id= "bkService"
class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean" >
         <property name= "target" ref= "bankServiceTarget" />
         <property name= "transactionManager" ref= "transactionManager" />
         <property name= "transactionAttributes" >
             <props>
                 <prop key= "transfer" >PROPAGATION_REQUIRED</prop>
             </props>
         </property>
     </bean>

我们把这种配置方式称为 Spring 经典的声明式事务管理。

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

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

&nbsp;<!-- tx命名空间方式 -->
     <tx:advice id= "bkAdvicetx" transaction-manager= "transactionManager" >
         <tx:attributes>
             <tx:method name= "transfer" propagation= "REQUIRED" />
         </tx:attributes>
     </tx:advice>
 
     <aop:config>
         <aop:pointcut expression= "execution(* *.transfer(..))"
          id= "bkPointCut" />
         <aop:advisor advice-ref= "bkAdvice" pointcut-ref= "bkPointCut" />
     </aop:config>


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

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

Spring2.x还引入了基于Annotation的方式,具体主要涉及到@Transactional标注。
  @Transactional可以作用于接口、接口方法、类以及类方法上。
  当作用于类上时,该类的所有 public方法都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

&nbsp; @Transaction (propagation=Propagation.REQUIRED)
public boolean transfer(Long fromId,Long toId, double amount){
    return bankDao.transfer(fromId,toId,amount);
}

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

<tx:annotation-driven transaction-manager= "transactionManager" />

transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,否则将被忽略,也不会抛出任何异常。

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

猜你喜欢

转载自li200429.iteye.com/blog/1568110