java技术研究

统计

留言簿(3)

阅读排行榜

评论排行榜

Spring事务分析(2)--基于声明式的事务管理实现分析(转)

借助与spring AOP,spring提供了强大的基于声明式事务管理方式,它很好对事务管理代码和具体业务逻辑进行了解藕,使我们在coding过程不要去关心事务管理的逻辑。下面我们借助一个例子来将分析spring内部的实现。

1. 例子

1.1 datasource配置

  1.   <bean id="dataSource" class="com.taobao.tddl.jdbc.group.TGroupDataSource" init-method="init">   
  2.       <property name="appName" value="test" />  
  3.       <property name="dbGroupKey" value="test" />  
  4.   </bean>  
  5.   
  6.   
  7.  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  8. <property name="dataSource">  
  9.     <ref local="dataSource" />  
  10. </property>  
  11.   </bean>  
  12.   
  13.    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  
  14. <property name="configLocation">   
  15.     <bean class="org.springframework.core.io.ClassPathResource">   
  16.         <constructor-arg><value>/sqlmap/sqlmap-auth-config.xml</value></constructor-arg>   
  17.     </bean>  
  18. </property>  
  19. <property name="dataSource">  
  20.     <ref local="dataSource" />  
  21. </property>  
  22.   </bean>  

1.2 事务aop部分

实际上对于aop的方式,对于所有的aop方式,声明式事务都支持。请参见spring AOP的几种方式及实现原理分析

这里选择比较简单的方式来说明问题,其他的方式实际上是换汤不换药。

1.2.1 目标对象类代码:

  1. public class UserDAOImpl extends SqlMapClientDaoSupport implements UserDAO {  
  2.     
  3.    public void doUpdate(...){  
  4.      this.getSqlMapClientTemplate().update(...);  
  5.    }  
  6. }  

1.2.2 配置

  1. <bean id="userDAO"    
  2.        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">    
  3.   
  4.        <property name="transactionManager" ref="transactionManager" />       
  5.        <property name="target" ref="userDAOTarget" />    
  6.         <property name="proxyInterfaces" value="com.alibaba.china.spring.dao.UserDao" />  
  7.        <property name="transactionAttributes">    
  8.            <props>    
  9.                <prop key="*">PROPAGATION_REQUIRED</prop>  
  10.            </props>    
  11.        </property>    
  12.    </bean>    
  13.   <bean id="userDAOTarget" class="UserDAOImpl">  
  14.       <property name="sqlMapClient" ref="sqlMapClient"/>  
  15.   </bean>  


2. 探究事务内部实现原理

2.1 原生jdbc事务管理方式


在事务管理分析之前,我们先来看看针对于原始jdbc方式的事务管理代码:
  1. try {     
  2. conn = DriverManager.getConnection(connectionUrl);     
  3.   
  4. //点禁止自动提交,设置为false    
  5. conn.setAutoCommit(false);      
  6. stmt = conn.createStatement();    
  7.    
  8. //数据库更新操作1     
  9. stmt.executeUpdate(sql1);      
  10. //数据库更新操作2     
  11. stmt.executeUpdate(sql2);    
  12.     
  13. //事务提交     
  14. conn.commit();     
  15.   
  16. }catch(Exception ex) {      
  17.    
  18. //操作不成功则回滚   
  19.  conn.rollback();     
  20. ex.printStackTrace();     
  21.     
  22. finally{  
  23. ...  
  24. }    

可以看到基于jdbc的事务管理最后是通过Connection来落实。针对于DataSource这样的事务资源的事务管理,经过简化抽取后最终的流程与上面实际上是一致的。

接下来,我们从AOP开始向下探究一下基于DataSource的spring事务实现原理。

2.2 事务代理对象的创建

首先我们来看TransactionProxyFactoryBean这个FactoryBean是如何将事务逻辑切入到具体业务逻辑中的。

了解spring AOP的人都知道,要定义一个完整的切面需要包括两个基本组件

一个是Pointcut,即定义在业务逻辑的哪个地方插入横切逻辑。

一个是Advise,即定义具体的横切逻辑。

TransactionProxyFactoryBean是如何将横切逻辑织入到业务逻辑的正确的位置,先来看看类图


完全在我们的意料之中,TransactionProxyFactoryBean包括两个属性,Pointcut,另一个TransactionInterceptor,TransactionInterceptor中实际上封装了一个业务方法的一个完整的事务处理流程,这个将在后面进行分析。

TransactionProxyFactoryBean作为一个FactoryBean,与其他创建AOP代理对象的FactoryBean相比,它只是针对于横切逻辑是事务这个特定的情况而已,他们又有很多逻辑是可以共用的,因此,spring把这部分逻辑抽取处理放入了AbstractSingletonProxyFactoryBean类中,该类通过暴露一个createMainInterceptor()方法,用于子类定制主要的(main)拦截器。代理对象的创建是在afterPropertiesSet()方法中,

  1. public void afterPropertiesSet() {  
  2.                ... ...  
  3.     ProxyFactory proxyFactory = new ProxyFactory();  
  4.   
  5.     //添加前置拦截器  
  6.   
  7.     // 添加由子类定制的拦截器  
  8.     proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));  
  9.   
  10.     //添加后置拦截器  
  11.              
  12.     proxyFactory.copyFrom(this);  
  13.     TargetSource targetSource = createTargetSource(this.target);  
  14.     proxyFactory.setTargetSource(targetSource);  
  15.   
  16.     //设置通过接口进行代理情况下的配置  
  17.   
  18.     this.proxy = getProxy(proxyFactory);  
  19. }  

实际上TransactionProxyFactoryBean中做的工作很少,主要配置TransactionInterceptor和Pointcut两个属性的相关参数。事务相关的参数都是最终都由TransactionInterceptor来管理。

我们来看看reateMainInterceptor()的实现

  1. protected Object createMainInterceptor() {  
  2.     this.transactionInterceptor.afterPropertiesSet();  
  3.     if (this.pointcut != null) { //若指定了pointcut,则创建指定pointcut的Advisor  
  4.         return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor);  
  5.     }  
  6.     else {  
  7.         //采用默认的pointcut  
  8.         return new TransactionAttributeSourceAdvisor(this.transactionInterceptor);  
  9.     }  
  10. }  

需要注意的是,pointcut只是指定哪些方法上添加事务处理。而具体每种方法上添加哪种属性类型的事务,是可以分别配置的,具体是通过TransactionInterceptor中的TransactionAttribute的来指定的。

如上面例子中的配置,

  1. <property name="transactionAttributes">    
  2.     <props>    
  3.         <prop key="*">PROPAGATION_REQUIRED</prop>  
  4.     </props>    
  5. </property>    
实际上会转换成NameMatchTransactionAttributeSource,该类内部通过一个map保存方法名和TransactionAttribute直接的映射关系。

若指定了pointcut,会优先使用pointcut来过滤一些不满足pointcut的织入点。如上面的例子上,虽然指定对于所有方法(key="*")都使用传播行为为PROPAGATION_REQUIRED的事务,但是若指定了pointcut之后,这个范围就被缩小到满足pointcut条件下的所有方法了。当然若满足pointcut,但不满足事务定义的配置信息,也不会为该方法添加事务

分析TranactionAttrbiuteSourceAdvisor代码可以知道,该类内部是以transactionInterceptor中定义的TransactionAttributeSource作为的pointcut的过滤规则。

2.3 横切逻辑的织入

上面提到整个事务的切入实际上都封装在TransactionInterceptor类中,由于这部分内容是实际业务逻辑和事务管理逻辑的结合处,不同逻辑的交汇处,往往也是复杂处。

TransactionInterceptor对有事务和无事务的情况都进行了考虑,这里我们只考虑有事务的情况。

先来看看类图结构:


首先TransactionInterceptor通过实现MethodInterceptor来处理横切逻辑。同样spring把一些与MethodInterceptor无关的事情放到了TransactionAspectSupport类中,该类中包含两个重要属性:

1. TransactionAttributeSource 管理具体方法与TransactionAttribute之间的映射,用于支持不同方法的事务定义配置(TransactionAttribute)。

2. PlatformTransactionManager 事务管理器,采用策略模式的方式把事务管理交由具体的接口实现类。

在这里已经出现了统一事务管理的三个接口中的两个。

在前面通过TransactionProxyFactoryBean,实际上已经帮我们做好了这些属性配置。接下来我们重点分析TransactionInterceptor的invoke方法,这是代理对象方法的入口。

该方法按照事务管理器的类型分为两种情况处理。

实际上第二种情况是为了支持CallbackPreferringPlatformTransactionManager这个回调的事务管理器接口,使用场景较少,在这里我们只分析第一种情况:

  1. public Object invoke(final MethodInvocation invocation) throws Throwable {  
  2.         ... ...  
  3.   
  4.         // 通过TransactionAttributeSource获取事务定义配置TransactionAttribute,若TransactionAttribute为null,则方法为非事务方法  
  5.         final TransactionAttribute txAttr =  
  6.                 getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);  
  7.         final String joinpointIdentification = methodIdentification(invocation.getMethod());  
  8.   
  9.         if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) {  
  10.             //根据事务定义,创建事务  
  11.             TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification);  
  12.             Object retVal = null;  
  13.             try {  
  14.                 //调用目标方法  
  15.                 retVal = invocation.proceed();  
  16.             }  
  17.             catch (Throwable ex) {  
  18.                 //处理目标方法异常  
  19.                 completeTransactionAfterThrowing(txInfo, ex);  
  20.                 throw ex;  
  21.             }  
  22.             finally {  
  23.                 cleanupTransactionInfo(txInfo);  
  24.             }  
  25.             commitTransactionAfterReturning(txInfo);  
  26.             return retVal;  
  27.         }  
  28.   
  29.         else {  
  30.             ... ...  
  31.         }  
  32.     }  
从上面的总的逻辑实际上很简单,把事务处理的过程放在try{}catch{}finally之中,这是通用的做法。具体说来包括一下几步:

1. 根据方法名获取方法对应事务配置信息。这一步直接从TransactionAttributeSource中获取。

2. 根据当前的事务配置,创建事务,并将创建的当前事务相关信息包括TransactionAttribute和TransactionStatus保存到TransactionInfo中,TransactionInfo是一个链表结构,除了当前事务信息外,它会保存当前线程的上一个事务。这样就可以支持方法调用栈上不同方法使用不同的事务属性,如代码下所示:

  1. //开始事务a  
  2. a(){    
  3.    //开始事务b,将事务a状态等信息保存  
  4.    b();   
  5.   //结束事务b,恢复事务a状态信息  
  6. }  
  7. //结束事务a  


具体实现在createTransactionIfNecessary方法中。

  1. protected TransactionInfo createTransactionIfNecessary(  
  2.             TransactionAttribute txAttr, final String joinpointIdentification) {  
  3.   
  4.         //创建一个代理TransactionAttribute,并用完整的方法名(joinpointIdentification)作为名称  
  5.   
  6.   
  7.   
  8.                 //调用PlatformTransactionManager创建事务,并返回事务状态  
  9.   
  10.         TransactionStatus status = null;  
  11.         if (txAttr != null) {  
  12.             PlatformTransactionManager tm = getTransactionManager();  
  13.             if (tm != null) {  
  14.                 status = tm.getTransaction(txAttr);<span style="color:#cc6600;">//TODO 重点</span>  
  15.             }  
  16.             else {  
  17.                 if (logger.isDebugEnabled()) {  
  18.                     logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +  
  19.                             "] because no transaction manager has been configured");  
  20.                 }  
  21.             }  
  22.         }               //将事务定义,状态,封装成一个TransactionInfo,并绑定到当前线程中(ThreadLocal),               //需要注意的是TransactionInfo在绑定到当前线程时,会把当前线程中原先的TransactionInfo保存起来。便于事务嵌套。return prepareTransactionInfo(txAttr, joinpointIdentification, status);}  

这里需要重点研究是的PlatformTransactionManager.getTransaction()方法,不同类型的事务这里实现会不同。后面将会以例子中的DataSourceTransactionManager为例分析这个实现过程。

3. 调用目标方法。当然在这里面还有可能调用其它方法,并且也使用了事务管理,形成事务重叠,但还是使用上面的这份代码,一样的逻辑,因此不再这样递归分析。后面将会以例子中分析spring是从如何从底层让目标方法支持事务的。

4. 目标方法执行异常后的处理,通过TransactionAttribute的回调接口判断对于抛出的异常类型回滚还是继续提交。

  1. protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {  
  2. if (txInfo.transactionAttribute.rollbackOn(ex)) {  
  3.     getTransactionManager().rollback(txInfo.getTransactionStatus());  
  4. }else{  
  5.     getTransactionManager().commit(txInfo.getTransactionStatus());}  
  6. }  

5. 在finally中进行清理工作,主要把老的TransactionInfo替换回去,使当前事务的上一个事务能够继续。

6.目标方法正常结束,则提交事务。

这其实是一个很通用事务处理逻辑过程,接下来我们具体分析PlatformTransactionManager如何创建事务,回滚及提交的。

2.4 事务管理器

PlatformTransactionManager抽象了事务管理的三个统一的入口方法

  1. public interface PlatformTransactionManager {  
  2.   //创建事务  
  3.   TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;  
  4.   //提交事务  
  5.   void commit(TransactionStatus status) throws TransactionException;  
  6.   //回滚事务  
  7.   void rollback(TransactionStatus status) throws TransactionException;  
  8. }  

以DataSourceTransactionManager为例分析:


AbstractPlatformTransactionManager封装了采用模板模式规定了三个事务管理方法的基本逻辑,而将与具体事务资源相关的内容通过抽象方法开放给子类实现。具体说来主要包括以下几个方面:

1. 对于getTransaction()方法:

1.1 判断是否存在当前事务,然后根据当前事务存在与否进行不同的操作。

1.2 结合当前是否存在当前事务,然后根据根据传入的事务定义的传播行为执行后续的逻辑,如挂起当前事务,创建事务失败则恢复当前事务等。

2.对于commit()方法

在提交事务之前检查事务是否是readOnly,若是,则用回滚代替事务提交。

3.对于rollback() 方法,

在事务回滚后,清理并恢复事务的状态。

接下来从代码层面分析一下三个方法的大体逻辑

2.4.1 创建事务

创建事务的逻辑定义在AbstractPlatformTransactionManager类的getTransaction()方法中,中间省去一些相对不重要的代码(用//...注释代替)

  1. public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {  
  2.         Object transaction = doGetTransaction(); //获取事务对象,这里是暴露给子类来提供。  
  3.   
  4.         //... 获取日志是否是debug级别  
  5.   
  6.         if (definition == null) {  
  7.             //没有传入事务定义信息,则采用默认配置。  
  8.             definition = new DefaultTransactionDefinition();  
  9.         }  
  10.                 //如果存在当前事务,则根据事务传播行为,定义接下来事务的创建逻辑。  
  11.         if (isExistingTransaction(transaction)) {  
  12.             return handleExistingTransaction(definition, transaction, debugEnabled);  
  13.         }  
  14.                 //不存在当前事务,则着手创建新的事务  
  15.         //事务超时时间配置错误,直接抛异常  
  16.         if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {  
  17.             throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());  
  18.         }  
  19.                 //接着根据事务定义的传播行为创建事务  
  20.         // 传播行为为:PROPAGATION_MANDATORY,而当前事务不存在,很明显,只能抛异常了,其他情况,则可以继续  
  21.   
  22.         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {  
  23.             throw new IllegalTransactionStateException(  
  24.                     "No existing transaction found for transaction marked with propagation 'mandatory'");  
  25.         }  
  26.         else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||  
  27.                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||  
  28.             definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
  29.             SuspendedResourcesHolder suspendedResources = suspend(null); //挂起事务  
  30.               
  31.             try {  
  32.                 doBegin(transaction, definition); //调用子类的模板方法,创建新事务  
  33.             }  
  34.             catch (TransactionException ex) {  
  35.                 resume(null, suspendedResources); //抛出异常则,恢复挂起的事务  
  36.                 throw ex;  
  37.             }  
  38.             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); //判断是否需要同步  
  39.             return newTransactionStatus(  
  40.                     definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);  
  41.         }  
  42.         else {  
  43.               
  44.             boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);  
  45.             return newTransactionStatus(definition, nulltrue, newSynchronization, debugEnabled, null);  
  46.         }  
  47.     }  

针对于DataSourceTransactionManager的doGetTransaction()方法实现,从代码看,逻辑很简单:

  1. protected Object doGetTransaction() {  
  2.     DataSourceTransactionObject txObject = new DataSourceTransactionObject();  
  3.     txObject.setSavepointAllowed(isNestedTransactionAllowed());  
  4.     ConnectionHolder conHolder =  
  5.         (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);  
  6.     txObject.setConnectionHolder(conHolder, false);  
  7.     return txObject;  
  8. }  
它会创建一个并返回一个DataSourceTransactionObject,它会被设置两个属性,一个是否支持savepoint,一个对用于保存数据库连接的ConnectionHolder。ConnectionHolder是通过TransactionSynchronizationManager从当前线程的ThreadLocal中获取,TransactionSynchronizationManager相当于是一个事务资源管理器。目前只看到通过TransactionSynchronizationManager从当前线程中获取ConnectionHolder,那它又是何时绑定到当前线程的呢?不急,后面肯定会碰到。

作为基于DataSource的事务管理,实际上数据库连接Connection是最为核心的资源,事务的管理控制最终都会由Connection的相关方法来完成。


isExistingTransaction()方法实际上是根据ConnectionHolder中Connection是否存在,并且是active来判断的。

根据这个状态分为两种情况


a. 当前事务存在


对于当前事务存在的情况,由handleExistingTransaction()方法统一处理,该方法主要是根据definition中指定事务传播行为和当前事务的关系进行处理。

在分析之前先了解spring中定义的传播行为,便于我们理解代码的逻辑:

  1. PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。   
  2. PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。   
  3. PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。   
  4. PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。   
  5. PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。   
  6. PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。   
  7. PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。   
  8. 前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。   
  9. 它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)   
如果不支持当前事务,抛异常

  1. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {   
  2.             throw new IllegalTransactionStateException(  
  3.                     "Existing transaction found for transaction marked with propagation 'never'");  
  4.         }  
如果不需要事务,则挂起当前事务,返回一个不需要事务的TransactionStatus

  1. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {  
  2.             Object suspendedResources = suspend(transaction);  
  3.             boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);  
  4.             return newTransactionStatus(  
  5.                     definition, nullfalse, newSynchronization, debugEnabled, suspendedResources); //注意第二参数为null  
  6.         }  
如果需要创建一个新事务,则首先挂起当前事务,然后调用子类doBegin()方法重新定义transaction,最后封装成TransactionStatus中。

  1. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {  
  2.               
  3.             SuspendedResourcesHolder suspendedResources = suspend(transaction);  
  4.             try {  
  5.                 doBegin(transaction, definition);  
  6.             }  
  7.             catch (TransactionException beginEx) {  
  8.                 try {  
  9.                     resume(transaction, suspendedResources);  
  10.                 }  
  11.                 catch (TransactionException resumeEx) {  
  12.                   
  13.                     throw resumeEx;  
  14.                 }  
  15.                 throw beginEx;  
  16.             }  
  17.             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);  
  18.             return newTransactionStatus(  
  19.                     definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);  
  20.         }  

在DataSourceTransactionManager.doBegin()实现中,会判断当前事务没有绑定Connection,如果没有,则通过dataSource获取新的连接,然后会根据事务定义的属性,如隔离级别,超时时间设置到Connection中,并将Connection的自动提交设置为false(setAutoCommit(false)),最后会调用

  1. TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());  
将新产生的ConnectionHolder采用ThreadLocal的方式绑定到当前线程中。这也就回答了我们前面提出的问题。

对于传播行为是PROPAGATION_NESTED,即嵌套事务(嵌套事务是已经存在事务的一个真正的子事务. 潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. ),由定义可知,它会先检查底层事务资源是否支持savepoint,支持则创建在当前事务上创建一个TransactionStatus,并创建并保存嵌套事务开始的savepoint,便于后面回滚操作。对于不支持savepoint的资源,直接创建事务状态返回。

  1. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
  2.             if (!isNestedTransactionAllowed()) {  
  3.                 throw new NestedTransactionNotSupportedException(  
  4.                         "Transaction manager does not allow nested transactions by default - " +  
  5.                         "specify 'nestedTransactionAllowed' property with value 'true'");  
  6.             }  
  7.               
  8.             if (useSavepointForNestedTransaction()) {  
  9.                 // Create savepoint within existing Spring-managed transaction,  
  10.                 // through the SavepointManager API implemented by TransactionStatus.  
  11.                 // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.  
  12.                 DefaultTransactionStatus status =  
  13.                         newTransactionStatus(definition, transaction, falsefalse, debugEnabled, null);  
  14.                 status.createAndHoldSavepoint();  
  15.                 return status;  
  16.             }  
  17.             else {  
  18.                 // Nested transaction through nested begin and commit/rollback calls.  
  19.                 // Usually only for JTA: Spring synchronization might get activated here  
  20.                 // in case of a pre-existing JTA transaction.  
  21.                 doBegin(transaction, definition);  
  22.                 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);  
  23.                 return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);  
  24.             }  
  25.         }  

对于其他情况,都会直接构建TransactionStatus返回。

b. 当前事务不存在


对于当前事务不存在的情况 即isExistingTransaction()返回false的情况下。

在事务传播行为为PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED下,会先调用一个入参为null的suspend(null) 方法,参考suspend源码可以知道,这样做实际上为了给注册在TransactionSynchronizationManager中注册的TransactionSynchronization一个事件触发。

从TransactionSynchronization接口定义可以看到它主要关注以下事件:

  1. public interface TransactionSynchronization {  
  2. void suspend();//事务挂起事件  
  3. void resume();//事务恢复事件  
  4. void beforeCommit(boolean readOnly);//事务提交之前  
  5. void beforeCompletion();//事务提交或回滚之前  
  6. void afterCommit();//事务提交之后  
  7. void afterCompletion(int status);//事务提交或回滚完成后  
  8. }  

事务挂起的操作因具体的事务资源的不同而不同,因此具体的挂起逻辑交由子类实现,子类会将挂起的当前事务资源返回,最后被封装到SuspendedResourcesHolder中。若在在调用doBegin过程中出现异常,则通过resume()方法恢复被挂起的事务,这里还是会先给TransactionSynchronizationManager中注册的TransactionSynchronization一个事件触发,具体恢复工作交由子类实现。

2.4.2 事务回滚

rollback()方法的主要逻辑是在processRollback()中

  1. private void processRollback(DefaultTransactionStatus status) {  
  2.         try {  
  3.             try {  
  4.                 triggerBeforeCompletion(status);  
  5.                 if (status.hasSavepoint()) {  
  6.                     status.rollbackToHeldSavepoint();  
  7.                 }  
  8.                 else if (status.isNewTransaction()) {  
  9.                     doRollback(status);  
  10.                 }  
  11.                 else if (status.hasTransaction()) {  
  12.                     if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {  
  13.                         doSetRollbackOnly(status);  
  14.                     }  
  15.                 }  
  16.                   
  17.             }  
  18.             catch (RuntimeException ex) {  
  19.                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);  
  20.                 throw ex;  
  21.             }  
  22.             catch (Error err) {  
  23.                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);  
  24.                 throw err;  
  25.             }  
  26.             triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);  
  27.         }  
  28.         finally {  
  29.             cleanupAfterCompletion(status);  
  30.         }  
  31.     }  

总结起来,对于processRollback方法,实际上做了3个工作

  • 触发TransactionSynchronization事件

在事务回滚开始工作之前,触发beforeCompletion事件

事务回滚工作结束之后,清理工作之前,触发afterCompletion事件。

同样创建事务的事件触发一样,是通过TransactionSynchronizationManager来进行。

  • 回滚事务

回滚事务分为几种情况:

1.若事务是嵌套事务,则回滚到上一个savepoint

2.若TransactionStatus中状态表示这是一个新事务,则将回滚的逻辑交由子类实现。很明显,对于DataSourceTransactionManager最终会调用Connection.rollback()方法实现事务回滚。

3.对于其他存在事务的情况,则将事务被设置为rollbackOnly,就通过子类来实现doSetRollbackOnly。DataSourceTransactionManager中,实际上就是将保存在TransactionStatus中的DataSourceTransactionObject对象设置为rollbackOnly

  • 在finally中清理进行事务清理

1. 设置TransactionStatus为completed

2. 清理TransactionSynchronizationManager

3. 在当前事务之前有事务挂起,则恢复。


2.4.3 事务提交

这里有两种情况:

一种是事务设置为rollbackOnly, 设置了这个状态后,事务的意图已经很明显,即进行回滚,因此它就直接调用processRollback()方法回滚

另一种就是提交,它涉及到:

1. 从TransactionSynchronization接口已经知道,对于事务提交,它会关注4个事件。先后关系为:

beforeCommit-》beforeCompletion-》提交操作-》afterCompletion-》afterCommit

2.对于嵌套事务,清除savePoint,此时事务不应该提交,提交操作应交由外部的事务来处理。

3.对于新事务,将提交操作交由子类来进行。很明显,基于DataSourceTransactionManager最后会将提交操作落实到Connection.commit()方法。


2.4. 偷梁换柱之Connection管理

针对于DataSourceTransactionManager,虽然spring通过dataSource获取了Connection,并进行了事务相关属性的配置,如何影响到targetClass执行的代码?且看spring是如何在底层将Connection偷梁换柱的。

我们知道,基于Datasource的事务管理,最终都会交由Connection来处理。若spring在事务管理器中设置的Connection,与最终DAO所用的Connection不是同一个Connection,那么Spring就白忙活了,DAO是不会听事务的控制的,该干嘛干嘛。

为了避免这样的情况,Spring再一次采取了它的强有力的方式--代理。

2.4.1 在DataSource上动手脚

基于IBatis的数据库访问方式中,我们通常会采用这样的方式获取sqlMapClient对象

  1.    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  
  2. <property name="configLocation">   
  3.     <bean class="org.springframework.core.io.ClassPathResource">   
  4.         <constructor-arg><value>/sqlmap/sqlmap-auth-config.xml</value></constructor-arg>   
  5.     </bean>  
  6. </property>  
  7. <property name="dataSource">  
  8.     <ref local="dataSource" />  
  9. </property>  
  10.   </bean>  
对,又是一个FactoryBean,可以知道它肯定又作了代理。

SqlMapClientFactoryBean实现了FactoryBean和InitializingBean两个接口,它将创建sqlMapClient的逻辑放到了afterPropertiesSet()方法中。

  1. public void afterPropertiesSet() throws Exception {  
  2. ... ...  
  3.   
  4.                    this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);  
  5.   
  6.             // Tell the SqlMapClient to use the given DataSource, if any.  
  7.             if (this.dataSource != null) {  
  8.                 TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();  
  9.                 DataSource dataSourceToUse = this.dataSource;  
  10.                 if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {  
  11.                     dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);  
  12.                 }  
  13.                 transactionConfig.setDataSource(dataSourceToUse);  
  14.                 transactionConfig.initialize(this.transactionConfigProperties);  
  15.                 applyTransactionConfig(this.sqlMapClient, transactionConfig);  
  16.             }  
  17.   
  18. ... ...   
  19. }  

首先通过buildSqlMapClient()创建sqlMapClient,这里面实际上就是通常的ibatis初始化工作,SqlMapconfigParser解析sqlmapconfig.xml配置文件,便可得到实例化后的sqlMapClient. 另外,SqlMapClientFactoryBean还支持直接指定sqlMap映射文件。至于如何解析ibatis配置文件及sqlMapClient的实例化过程,不在本篇讨论范围之内,后面会另外专门讨论。

得到sqlMapClient后,会检查是否指定了datasource属性,如果指定了就用指定的datasource设置到sqlMapClient中。这里并不会直接交给sqlmapClient一个原生的dataSource,而是动过手脚之后的。

首先,spring会用TransactionAwareDataSourceProxy把原生的数据源包裹起来,然后把它设置到ibatis的事务配置里面,当作事务的数据源。然后初始化事务配置:

  1. transactionConfig.initialize(this.transactionConfigProperties);  
可以看到在SqlMapClientFactoryBean构造方法中初始化transactionConfigProperties的内容:

  1. this.transactionConfigProperties.setProperty("SetAutoCommitAllowed""false");  
很明显,不允许自动提交。

最后用替换掉Ibatis本身的事务管理器,干净利落!


且来看看TransactionAwareDataSourceProxy做了些什么事情。
类结构图:


可以看出是典型的代理设计模式。

对于getConnection方法

  1. public Connection getConnection() throws SQLException {  
  2.         DataSource ds = getTargetDataSource();  
  3.         Assert.state(ds != null"'targetDataSource' is required");  
  4.         return getTransactionAwareConnectionProxy(ds);  
  5.     }  
  1. protected Connection getTransactionAwareConnectionProxy(DataSource targetDataSource) {  
  2.         return (Connection) Proxy.newProxyInstance(  
  3.                 ConnectionProxy.class.getClassLoader(),  
  4.                 new Class[] {ConnectionProxy.class},  
  5.                 new TransactionAwareInvocationHandler(targetDataSource));  
  6.     }  
采用的是jdk的动态代理方式,具体的代理逻辑封装在TransactionAwareInvocationHandler中。

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  2.                         //... ... 对于equals,hashCode等方法的代理  
  3.                         else if (method.getName().equals("close")) {  
  4.                 // Handle close method: only close if not within a transaction.  
  5.                 DataSourceUtils.doReleaseConnection(this.target, this.targetDataSource);  
  6.                 this.closed = true;  
  7.                 return null;  
  8.             }  
  9.   
  10.             if (this.target == null) {  
  11.                 if (this.closed) {  
  12.                     throw new SQLException("Connection handle already closed");  
  13.                 }  
  14.                 if (shouldObtainFixedConnection(this.targetDataSource)) {  
  15.                     this.target = DataSourceUtils.doGetConnection(this.targetDataSource);  
  16.                 }  
  17.             }  
  18.             Connection actualTarget = this.target;  
  19.             if (actualTarget == null) {  
  20.                 actualTarget = DataSourceUtils.doGetConnection(this.targetDataSource);  
  21.             }  
  22.   
  23.             if (method.getName().equals("getTargetConnection")) {  
  24.                 // Handle getTargetConnection method: return underlying Connection.  
  25.                 return actualTarget;  
  26.             }  
  27.   
  28.             // Invoke method on target Connection.  
  29.             try {  
  30.                 Object retVal = method.invoke(actualTarget, args);  
  31.   
  32.                 // If return value is a Statement, apply transaction timeout.  
  33.                 // Applies to createStatement, prepareStatement, prepareCall.  
  34.                 if (retVal instanceof Statement) {  
  35.                     DataSourceUtils.applyTransactionTimeout((Statement) retVal, this.targetDataSource);  
  36.                 }  
  37.   
  38.                 return retVal;  
  39.             }  
  40.             catch (InvocationTargetException ex) {  
  41.                 throw ex.getTargetException();  
  42.             }  
  43.             finally {  
  44.                 if (actualTarget != this.target) {  
  45.                     DataSourceUtils.doReleaseConnection(actualTarget, this.targetDataSource);  
  46.                 }  
  47.             }  
  48. }  
TransactionAwareInvocationHandler中invoke方法的代码中可以看到:

对于代理对象的close方法,直接调用DataSourceUtils.doReleaseConnection()方法释放连接,在DataSourceUtils.doReleaseConnection()方法中,实际上最后会通过TransactionSynchronizationManager来获取绑定到ThreadLocal中的ConnectionHolder,并释放。代码如下:

  1. if (dataSource != null) {  
  2.     ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
  3.     if (conHolder != null && connectionEquals(conHolder, con)) {  
  4.         // It's the transactional Connection: Don't close it.  
  5.         conHolder.released();  
  6.         return;  
  7.     }  
  8. }  

同样,对于DataSourceUtils.doGetConnection(),最后也是通过TransactionSynchronizationManager.getResource()方法获取绑定到当前线程的Connection

在前面讲到过在事务创建阶段,DataSourceTransactionManager的方法doBegin()中会将当前事务相关的Connection绑定到当前线程:
  1. TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());  
这里在DAO访问数据库过程中实际上会通过调用:

  1. TransactionSynchronizationManager.getResource(dataSource)  
获取当前事务绑定的Connection,前后形成呼应,因此事务就这样偷偷的生效了。


2.4.2 模板方法中的替换

对于基于IBatis的数据访问方式,Spring为我们提供了一个方便用于数据库访问的模板类SqlMapClientTemplate。

该类提供一个基于回调的模板方法

  1. public Object execute(SqlMapClientCallback action) throws DataAccessException {  
  2. //... ...  
  3. SqlMapSession session = this.sqlMapClient.openSession();  
  4.         if (logger.isDebugEnabled()) {  
  5.             logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");  
  6.         }  
  7.         Connection ibatisCon = null;  
  8.   
  9.         try {  
  10.             Connection springCon = null;  
  11.             DataSource dataSource = getDataSource();  
  12.             boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);  
  13.   
  14.             // Obtain JDBC Connection to operate on...  
  15.             try {  
  16.                 ibatisCon = session.getCurrentConnection();  
  17.                 if (ibatisCon == null) {  
  18.                     springCon = (transactionAware ?  
  19.                             dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));  
  20.                     session.setUserConnection(springCon);  
  21.                 }  
  22.                       try {  
  23.                 return action.doInSqlMapClient(session);  
  24.             }  
  25.             catch (SQLException ex) {  
  26.                 throw getExceptionTranslator().translate("SqlMapClient operation"null, ex);  
  27.             }  
  28.             finally {  
  29.                 try {  
  30.                     if (springCon != null) {  
  31.                         if (transactionAware) {  
  32.                             springCon.close();  
  33.                         }  
  34.                         else {  
  35.                             DataSourceUtils.doReleaseConnection(springCon, dataSource);  
  36.                         }  
  37.                     }  
  38.                 }  
  39.                 catch (Throwable ex) {  
  40.                       
  41.                 }  
  42.             }  
可以看到这里把获取dataSource的过程封装起来,对于直接给SqlMapClientTemplate设置的dataSource,在获取Connection时,又是通过DataSourceUtils.doGetConnection()方法获取,最终获取的是又是TransactionSynchronizationManager中绑定的Connection对象。否则,若dataSource是从sqlMapClient中获取的,此时dataSource已经是被代理的DataSource了。然后把Connection设置到SqlMapSession对象中,使此次操作在事务过程中。

对于最后connection的释放,若dataSource是被代理的dataSource(TransactionAwareDataSourceProxy),则直接调用它的close方法,从前文中的分析中可以知道,该close方法最后会通过DataSourceUtils.doReleaseConnection()来释放连接。

通过多个方面的措施,最终spring就能保证目标DAO的操作都在Spring事务管理器的控制范围内。


3. 总结

万变不离其宗,对于基于DataSource的事务管理,最终的简化后的逻辑就是我们在 2.1节中总结原生jdbc事务管理方式。spring只是对原生方式进行各种抽象及封装,通过AOP,利用IOC提供的方便,最终可以让用户方便的使用事务管理的方式。

转自:

http://blog.csdn.net/shuangyue/article/details/8981883

posted on 2013-06-06 11:45 小秦 阅读(842) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: