Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

事务学习(二)之事务使用

2017-06-10
nivelle

编程式事务管理

通过Spring进行编程式事务管理有两种方式,要么直接使用PlatformTransactionManager,要么使用更方便的TransactionTemplate.

  • 直接使用PlatformTransactionManager
 DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
  
  definition.setTimeout(20);  
  ...
  TransactionStatus txStatus = TransactionManager.getTransaction(definition);

  trr{
    //业务逻辑实现
    
  }catch(ApplicationException e){
    TransactionManager.rollback(txStatus);
    throw e;
  }catch(RunTimeException e){
    TransactionManager.rollback(txStatus);
    throw e;
  }catch(Error e){
    TransactionManager.rollback(txStatus);
    throw e;
  }
  TransactionManager.commit();
  

只要为transactionManager提供合适的PlatformTransactionManager实现,然后结合TransactionDefinition开启事务,并结合TransactionStatus来回滚或提交事务,就可以完成针对当前对象的整个事务管理。

  • 使用TransactionTemplate进行编程式事务管理

声明式事务管理

org.springframework.transaction.support.TransactionTemplatr对与PlatformTransactionManger相关的事务界定操作以及相关异常处理进行了模板化封装,开发人员更多地关注通过相应的Callback接口提供具体的事务界定内容即可。Spring针对TransactionTemplate提供了两个Callback接口,TransactionCallback和TransactionbackWithoutResult,二者唯一的区别是是否需要返回执行结果。

TransactionTemplate txTemplate = ...;

Object result = txTemplate.execute(new TransactionCallback(){
  public Object doInTraction(TransactionStatus txStatus){
     Object result =null;
     //各种事务操作
     return result;
  }
})

或者
txTemplate.execute(new TransactionCallbackWithoutResult(){
  @Override
  protected void  doInTransactionWithoutResult(TransactionStatus txStatus){
     // 事务操作1
     // 事务操作2
     //..
  }
})

TransactionTemplate会捕捉TransactionCallback或者TransactionCallbackWithoutResult事务操作中抛出的unchecked exception 并回滚事务,然后将unchecked exception 抛给上层处理。

所以,现在我们只需要处理特定于应用程序的异常即可,而不用直接使用PlatformTransactionManager那样,对所有可能的异常都进行处理。

如果事务处理期间没有任何问题,TransactionTemplate最终会为我们提交事务,唯一需要我们干预的只剩下某些情况的事务回滚了。如果在TransactionCallback或者TransactionCallbackWithoutResult的事务操作过程中需要让当前事务回滚而不是最终提交,一般来说,我们有如下两种方式。

  • 抛出unchecked exception ,TransactionTemplate会为我们处理事务回滚。如果事务操作中可能抛出checked exception,那么就得在callback内部捕获,然后转译为unchecked exception 后抛出。
txTemplate.execute(new TransactionCallbackWithoutResult(){
  @Override
  protected void doInTransactionWithoutResult(TransactionStatus txStatus){
   try{

     ...
   }catch(Che使用ckedException e){
     //抛出特定的异常类型以避免一般情况下使用RuntimeException
     throw new RuntimeException(e);
   }
  }
})

  • 使用Callback接口公开的TransactionStatus将事务标记为rollBackOnly.TransactionTemplate在最终提交事务的时候,如果检测到rollBackOnly标志状态被设置,将把提交事务改为回滚事务。
txTemplate.execute(new TransactionCallbackWithoutResult(){
  @Override
  protected void doInTransactionWithoutResult(TransactionStatus txStatus){
    boolean needRollback = faallse;
    ...
    if(needRollback)

     txStatus.setRollbackOnly();

  }

})

对于事务操作中可能抛出的checked exception ,如果既想回滚事务,又不想让它以unchecked exceptin的形式向上层传播的话,我们可以通过TransactionStatus设置rollBackOnly来达到目的。

txTemplate.execute(new TranactionCallbackWithoutResult(){
  @Override
  protected void doInTransactionWithoutResut(TransactionStatus txStatus){

   try{
     ...
   }catch(CheckedException e){
     logger.warn("Transaction is Rolled back!",e);
     txStatus.setRollbackOnly();
   }
  }
})


限制:虽然使用TransactionTemplate比PlatformTransactionManager更加便捷,但TransactionTemplate无法处理当事物操作中需要向上抛出原来的checked exception的情况。在TransacionCallback或者TransactionCallbackWithoutResult的方法定义中没有声明抛出任何checked exception ,直接使用PlatformTransactionManager则没有这样的限制。

编程创建基于Savepoint的嵌套事务

TrannsactionStatus不但可以在事务处理期间通过setRollbackOnly()方法来干预事务的状态,如果需要,作为SavepointManger,它可以帮助我们使用Savepoint机制来创建嵌套事务。

场景:银行账户转账,现在不是从一个账户转到另一个账户,而是从一个账户转到两个账户,一个主账户,一个备用账户。如果向主账户转账失败,则将金额转入备用账户。总之,金额从一个账户取出后,必须存入两个账户中的其中一个,以保证整个事物的完整性。

txTemplate.execute(new TransactionCallbackWithoutResult(){
  
  @Override
  protected void doInTransactionWithoutResult(TransactonStatus txStatus){

    try{
      withdraw("WITHDRAW_ACOUNT_ID",transferAmount);
      Bigdecimal tansferAmount = new BigDecimal("20000");
      try
      {
        deposit("WITHDRAW_ACOUNT_ID",transferAmount);

        Obejct savePointBeforeDeposit = txStatus.createSavepoint();
        try{
           deposit("WITHDRAW_ACOUNT_ID",transferAmount);
        }catch(DepositExceon ex)
        {
          logger.warn("rollback to savepoint for main transfer failure",ex);
          txStatus.rollbackToSavepoint(savePointBeforeDeposit);
           deposit("WITHDRAW_ACOUNT_ID",transferAmount);
        }finall{
          txStatus.releaseSavePoint(savePointBeforeDeposit);
        }
      }
    }catch(TransferException e){
       logger.warn("failed to complete transfer operation!",e);
       txStatus.setRollbackonly();
    }
  }
})

这里,使用Savepint创建嵌套事务的好处是,即使deposit过程中涉及多笔数据的更新,通过txStatus.rolbackToSavepoint(savePointBeforeDeposit)也可以将这些数据恢复到没有存入金额之前的状态,而不会破坏当前事务的完整性。如果传播特性是PROPAGATION_REQUIRES_NEW的TransactionDefinition创建一个新的事务的话,虽然deposit过程中出现问题也可以回滚,但取款与存款的操作就不在同一个事务中了。

  • XML元数据驱动的声明式事务

引子:事务管理本身就是一种横切关注点,与其他的横切关注点本质上一样,所以完全可以提供相应的Advice实现,然后织入到系统中需要该横切逻辑的Joinpoint处。提供一个拦截器,在业务方法执行开始之前开启一个事务,当方法执行完成或者异常退出的时候就提交事务或者回滚事务。有了Spring的编程式事务管理API的支持。

用于声明式事务管理的拦截器原型代码示例:


public class PrototypeTransactionInterceptor implements MethodInterceptor{
  
  private PlatformTransactionManager transactionManager;
  
  public Object invoke(MethodInvocation invocation) throws Throwable{
 
     Method method = invocation.getMethod();
     TransactionDefinition definition = getTransactionDefinitionByMethod(method);
     
     TransactionStatus txStatus = transactionManager.getTransaction(definition);
     
     Object result = null;
     try{
       result = invocation.proceed();
     }catch(Throwable t){
       if(needRollbackOn(t)){
         transactionManager.rollback(txStatus);
       }else{
         transactionManager.commit(txStatus);
       }
       throw toString();
     }
     transactionManager.commit(txStatus);
     return result;    
  }
  protected boolean needRollbackOn(Throwable t){
    //TODO 实现更多细节
    return false;
  }
  
  protected TransactionDefinition getTransactionDefinitionByMethod(Method method) {
    //TODO实现更多细节    
    return null;
  }
  
  public PlatformTransactonManager getTransactionManager(){
    return transactionManager;
  }
  
  public setTransactionManger(PlatformTransactionManager transactonManager){
    this.transactionManager = transactonManager;
  }
}

实现这样一个拦截器的过程中,我们会发现有些逻辑不好确定:

  • 针对每个对象业务方法的拦截,需要知道该方法是否需要事务支持。如果需要,针对该事物的TransactionDefinition相关信息又从哪里来获取?

  • 如果调用方法过程中抛出异常,如何对这些异常进行处理,那些异常抛出的情况下需要回滚事务,那些又不需要

我们需要提供某种方式来记载业务方法与对应的事务信息之间的映射关系,拦截器只要查询这种映射关系,就可以知道要不要为当前业务方法创建事务。如果要创建事务,则以业务的方法作为标志,到映射关系中查找创建事务所需要的信息,然后创建事务。

这种映射信息,叫做驱动事务的元数据(Metadata).有了类似于PrototypeTransactionInterceptor这样的AOP装备的支持,系统的事务管理就变成了只需要在元数据中声明相应的事务控制信息。

spring提供了用于声明事务管理的一切设施(使用org.springframework.transaction.interceptor.TransactionInterceptor实现代替PropertypeTransactionInterceptor)对于我们来说,所要做的只是决定要使用XML元数据,还是使用注解元数据的申明式事务管理。

从Spring 1.x 到 2.x大体上来说,我们可以使用以下4种方式配置方式在Ioc容器的配置文件中指定事务需要的元数据。

  • 使用ProxyFactory +TransactionInterceptor

  • 使用一站式的TransactionProxyFactoryBean

  • 使用BeanNameAutoProxyCreator

  • 使用Spring 2.x的申明事务配置方式

模型:QuoteService,先暂且不管它是应用于一般的证券系统还是外汇系统,总之,通过它,我们能够查询报价信息,并且必要的话,也可以更新底层数据内容。现在的QuoteService不是一个远程服务,它的目的很简单,基本上就是实现一个Quote信息的基本管理功能。

public interface IQuoteService{
  Quote getQuate();
  Quote getQuateByDateTine(DateTime dateTime);
  void saveQuote(Quote quote);
  void updateQuote(Quote quote);
  void deleteQuote(Quote quote);
}

...

public class QuoteService implements IQuoteService {
  
  private JdbcTemplate jdbcTemplate;

  @Override
  public Quote getQuate() {
    return (Quote)getJdbcTemplate().queryForObject("",new RowMapper(){
      
      public Object mapRow(ResultSet rs,int row) throws SQLException{
        Quote quote = new Quote();
        //...
        return quote;
      }
    })
    return null;
  }

  @Override
  public Quote getQuateByDateTine(DateTime dateTime) {
    // TODO Auto-generated method stub
    throw new NotImplementedException();
  }

  @Override
  public void saveQuote(Quote quote) {
    throw new NotImplementedException();

  }

  @Override
  public void updateQuote(Quote quote) {
    throw new NotImplementedException();

  }

  @Override
  public void deleteQuote(Quote quote) {
    throw new NotImplementedException();
  }

}

##### 使用ProxyFactory +TransactionInterceptor

XML元数据驱动的声明式事务,本质上来说,是为TransactionInterceptor提供需要的TransactionDefinition(具体来说,是TransactionAttribute)。而至于说将TransactionInterceptor给出事务管理相关的横切逻辑加到相应的业务对象上的过程,则纯粹是一个AOP的配置过程。既然如此,最基本的方式当然就是,直接通过ProxyFactoryBean完成事务管理这一横切关注点到系统的织入工作。


  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
  </bean>


  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>

  <bean id="quoteServiceTarget" class="...QuoteService">
    <property name="jdbcTemplate" ref="jdbcTemplate" />
  </bean>

  <bean id="quoteService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="qutoServiceTarget" />
    <property name="proxyInterfaces" value="...IQuoteService" />
    <property name="interceptorNames">
      <list>
        <value>transactionInterceptor</value>
      </list>
    </property>

  </bean>

  <bean id="client" class="...QuoteServiceClient">
    <property name="quoteService" ref="quoteService" />
  </bean>

 </beans>


要使声明式事务起作用,需要几个方面协调工作,这包括事务管理需要的具体数据资源类型、采用的数据访问技术、特定的事务管理器实现,以及TransactionInterceptor提供的业务方法拦截功能。具体到我们的QuoteService来说,我们要对数据库提供的数据资源进行事务操作,所以,配置中需要dataSourece定义,这是事务操作的基础,也就是如下配置的内容:


 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost/databaseName" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="username" value="..." />
    <property name="password" value="..." />
 </bean>

TransactionInterceptor是整个声明事务的主体,要让它发挥事务管理的职能,需要为它提供一个事务管理器。要让它知道该为那个方法加诸什么事务,是否加诸事务,需要为它提供必要的映射信息。


 <bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
      <props>
        <prop key="getQuate*">PROPAGATION_SUPPORTS,readOnly,time_20</prop>
        <prop key="saveQuate*">PROPAGATION_REQUIED</prop>
        <prop key="updateQuate*">PROPAGATION_REQUIED</prop>
        <prop key="deleteQuate*">PROPAGATION_REQUIED</prop>
      </props>
    </property>
  </bean>

以String 形式给出的事务属性是有一定规则的,这个规则由org.springframework.transaction.interceptor.TransactionAttributeEdtor类的逻辑来界定,TransactionAttributeEditor负责将String形式的事务属性转换为具体的TransactionAttrribute实例。

使用一站式的TransactionProxyFactoryBean

它是专门面向事务管理的ProxyFactoryBean,它直接将Transaction可以。 Interceptor纳入自身进行管理。同时不需要单独声明TransactionInterceptor的bean定义,有关事务的元数据、事务管理等信息,全部通过TransactionProxyFactory的bean定义指定就可以。

使用Spring 2x的声明事务配置方式

Spring 2.x 后提供的基于XML Schema的配置方式,专门为事务管理提供一个单独的命名空间,用于简化配置,结合新的tx命名空间,现在的事务管理看起来要清晰许多。


  <aop:config>
    <aop:pointcut id ="txServices" expression="execution(*cn.spring21.unveilspring.IQutoteService.*(..))"/>
    <aop:advisor pointcut-ref="txServices" advice-ref="txAdvice"/>  
  </aop:config>
  
  <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
      <tx:method name ="getQuate*" propagation="SUPPORTS" read-only="true" timeout="20"/>
       <tx:method name ="saveQuate*"/>
        <tx:method name ="updateQuate*"/>
         <tx:method name ="deleteQuate*"/>
    </tx:attributes>  
  </tx:advice>

<tx:advice>是专门为声明事务Advice而设置的配置元素,底层当然还是我们的TransactionInterceptor,只是披了一件新衣裳而已。的transaction-manager指定了它要使用的事务管理器是哪一个。如果容器中事务管理器的beanName恰好是transactionManager,那么可以不明确指定。内部由提供申明式事务所需要的元据映射信息,每条映射信息对应一个元素声明。只有name属性是必须指定的,其他属性代表事务定义的其他内容,propagation用于指定传播行为,isolation用于指定隔离级别,timeout用于指定事务的超时时间等。如果不明确指定的话,将采用DefaultTransactinDefinition的设置内容。

  • 注解元数据驱动的声明式事务

原理:将对应业务方法的事务元数据,直接通过注解标注到业务方法或者业务方法所在所在的对象上,然后在业务方法执行期间,通过反射读取标注在该业务方法上的注解所包含的元数据信息,最终根据读取的信息为业务方法构建事务管理支持。

Spring定义了org.springframework.transaction.Transactionl用于标注业务方法所对应的事务元数据信息。通过Transactional,可以指定与几乎相同的信息。当然,现在不用指定方法名称了,因为Transactional直接标注到了业务方法所在的对象定义上。

@Target({ElementType.METHOD,ElementTupe.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional
{
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout()defatul false;
Class<?extends Throwable>[] rollbackFor() default{};
String[] rollbackForClassName() default{};
Class<? entends Throwable>[] noRollbackFor() default{};
String[] noRollbackForClassName() default{};
}



@Transactional
public class QuoteService implements IQuoteService
{
private JdbcTemplate jdbcTemplate;

@Transactional(proagation=Propagation.SUPPORTS,readOnly=true,timeout=20)
public Quote getQuate(){
  return (Quote)getJdbcTemplate().queryForObject("selete * from fx_quote where quite_id=2",new RowMapper(){
   public Object mapRow(ResultSet rs,int row) throws SQLException{
     Quote quote = new Quote();
      //...
      return quote
   }

  })}


  @Transactional(propagation=Propagation.SUPPORTS,readObly=true,timeout=20)
  public Quote getQuateByDateTime(DateTime dateTime){
    throw new NotImplementedException();
  }
}


方法级别上的@Transactional元数据信息优先于对象级别上的@Transactional 的事务管理元数据信息。

@Transactional 申明式事务实现原型代码示例

public Quote getQuate()
{
   try
   {
     Method method = quoteService.getClass().getDeclaredMethod("getQuate",null);
     boolean isTxAnnotationPresent = method.isAnnotationPresent();
     if(!isTxAnnitationPresent)
     {
       return (Quote)quoteService.getQutate();
     }

     Transactional txInfo = method.getAnnotation(Transactional.class);
     TransactonTemplate transactionTemplate = new TransactionaTemplate(transactionManager);
     if(!txInfo.propagation().equals(Propagation.REQUIRED)){
      transactionTemplate.setPropagationBehaviior(txinfo.propagation().value());
     }
     if(txInfo.readObly()){
      transactionTemplate.setReadOnly(true);
     }

     return (Quote)transactionTemplate.execute(new TransactionCallback(){
      public object doInTransaction(TransactionStatus txStatus){
        return quoteService.getQuate();
      }
      })
  
   }catch(SecurityException e){
     e.printStackTrace();
     return null;
   }cathc(noSuchMethod e){
     e.printStackTrace();
     return null;
   }
}

不过,我们不用自己去写这些底层逻辑了,通过在容器的配置文件中指定如下一行配置,这些搜寻注解,读取内容,构建事务等工作全部都由Spring的IOC容器搞定:

  • 使用@Transactional 标注相应的业务对象以及相关业务方法。这一步通常由业务对象的开发者统一负责。

  • 在容器配置未见中设定事务基础设施。我们添加以便有人使用我们标注的@Transactional,并且,需要给出响应的事务管理器,要进行事务管理,没有它可不行。


内容来自《Spring揭秘》 作者:王福强


评论