借助与spring AOP,spring提供了强大的基于声明式事务管理方式,它很好对事务管理代码和具体业务逻辑进行了解藕,使我们在coding过程不要去关心事务管理的逻辑。下面我们借助一个例子来将分析spring内部的实现。
1. 例子
1.1 datasource配置
- <bean id="dataSource" class="com.taobao.tddl.jdbc.group.TGroupDataSource" init-method="init">
- <property name="appName" value="test" />
- <property name="dbGroupKey" value="test" />
- </bean>
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource">
- <ref local="dataSource" />
- </property>
- </bean>
- <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <property name="configLocation">
- <bean class="org.springframework.core.io.ClassPathResource">
- <constructor-arg><value>/sqlmap/sqlmap-auth-config.xml</value></constructor-arg>
- </bean>
- </property>
- <property name="dataSource">
- <ref local="dataSource" />
- </property>
- </bean>
1.2 事务aop部分
实际上对于aop的方式,对于所有的aop方式,声明式事务都支持。请参见spring AOP的几种方式及实现原理分析
这里选择比较简单的方式来说明问题,其他的方式实际上是换汤不换药。
1.2.1 目标对象类代码:
- public class UserDAOImpl extends SqlMapClientDaoSupport implements UserDAO {
- public void doUpdate(...){
- this.getSqlMapClientTemplate().update(...);
- }
- }
1.2.2 配置
- <bean id="userDAO"
- class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
- <property name="transactionManager" ref="transactionManager" />
- <property name="target" ref="userDAOTarget" />
- <property name="proxyInterfaces" value="com.alibaba.china.spring.dao.UserDao" />
- <property name="transactionAttributes">
- <props>
- <prop key="*">PROPAGATION_REQUIRED</prop>
- </props>
- </property>
- </bean>
- <bean id="userDAOTarget" class="UserDAOImpl">
- <property name="sqlMapClient" ref="sqlMapClient"/>
- </bean>
2. 探究事务内部实现原理
2.1 原生jdbc事务管理方式
- try {
- conn = DriverManager.getConnection(connectionUrl);
- //点禁止自动提交,设置为false
- conn.setAutoCommit(false);
- stmt = conn.createStatement();
- //数据库更新操作1
- stmt.executeUpdate(sql1);
- //数据库更新操作2
- stmt.executeUpdate(sql2);
- //事务提交
- conn.commit();
- }catch(Exception ex) {
- //操作不成功则回滚
- conn.rollback();
- ex.printStackTrace();
- } finally{
- ...
- }
可以看到基于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()方法中,
- public void afterPropertiesSet() {
- ... ...
- ProxyFactory proxyFactory = new ProxyFactory();
- //添加前置拦截器
- // 添加由子类定制的拦截器
- proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));
- //添加后置拦截器
- proxyFactory.copyFrom(this);
- TargetSource targetSource = createTargetSource(this.target);
- proxyFactory.setTargetSource(targetSource);
- //设置通过接口进行代理情况下的配置
- this.proxy = getProxy(proxyFactory);
- }
实际上TransactionProxyFactoryBean中做的工作很少,主要配置TransactionInterceptor和Pointcut两个属性的相关参数。事务相关的参数都是最终都由TransactionInterceptor来管理。
我们来看看reateMainInterceptor()的实现
- protected Object createMainInterceptor() {
- this.transactionInterceptor.afterPropertiesSet();
- if (this.pointcut != null) { //若指定了pointcut,则创建指定pointcut的Advisor
- return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor);
- }
- else {
- //采用默认的pointcut
- return new TransactionAttributeSourceAdvisor(this.transactionInterceptor);
- }
- }
需要注意的是,pointcut只是指定哪些方法上添加事务处理。而具体每种方法上添加哪种属性类型的事务,是可以分别配置的,具体是通过TransactionInterceptor中的TransactionAttribute的来指定的。
如上面例子中的配置,
- <property name="transactionAttributes">
- <props>
- <prop key="*">PROPAGATION_REQUIRED</prop>
- </props>
- </property>
若指定了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这个回调的事务管理器接口,使用场景较少,在这里我们只分析第一种情况:
- public Object invoke(final MethodInvocation invocation) throws Throwable {
- ... ...
- // 通过TransactionAttributeSource获取事务定义配置TransactionAttribute,若TransactionAttribute为null,则方法为非事务方法
- final TransactionAttribute txAttr =
- getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
- final String joinpointIdentification = methodIdentification(invocation.getMethod());
- if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) {
- //根据事务定义,创建事务
- TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification);
- Object retVal = null;
- try {
- //调用目标方法
- retVal = invocation.proceed();
- }
- catch (Throwable ex) {
- //处理目标方法异常
- completeTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- finally {
- cleanupTransactionInfo(txInfo);
- }
- commitTransactionAfterReturning(txInfo);
- return retVal;
- }
- else {
- ... ...
- }
- }
1. 根据方法名获取方法对应事务配置信息。这一步直接从TransactionAttributeSource中获取。
2. 根据当前的事务配置,创建事务,并将创建的当前事务相关信息包括TransactionAttribute和TransactionStatus保存到TransactionInfo中,TransactionInfo是一个链表结构,除了当前事务信息外,它会保存当前线程的上一个事务。这样就可以支持方法调用栈上不同方法使用不同的事务属性,如代码下所示:
- //开始事务a
- a(){
- //开始事务b,将事务a状态等信息保存
- b();
- //结束事务b,恢复事务a状态信息
- }
- //结束事务a
具体实现在createTransactionIfNecessary方法中。
- protected TransactionInfo createTransactionIfNecessary(
- TransactionAttribute txAttr, final String joinpointIdentification) {
- //创建一个代理TransactionAttribute,并用完整的方法名(joinpointIdentification)作为名称
- //调用PlatformTransactionManager创建事务,并返回事务状态
- TransactionStatus status = null;
- if (txAttr != null) {
- PlatformTransactionManager tm = getTransactionManager();
- if (tm != null) {
- status = tm.getTransaction(txAttr);<span style="color:#cc6600;">//TODO 重点</span>
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
- "] because no transaction manager has been configured");
- }
- }
- } //将事务定义,状态,封装成一个TransactionInfo,并绑定到当前线程中(ThreadLocal), //需要注意的是TransactionInfo在绑定到当前线程时,会把当前线程中原先的TransactionInfo保存起来。便于事务嵌套。return prepareTransactionInfo(txAttr, joinpointIdentification, status);}
这里需要重点研究是的PlatformTransactionManager.getTransaction()方法,不同类型的事务这里实现会不同。后面将会以例子中的DataSourceTransactionManager为例分析这个实现过程。
3. 调用目标方法。当然在这里面还有可能调用其它方法,并且也使用了事务管理,形成事务重叠,但还是使用上面的这份代码,一样的逻辑,因此不再这样递归分析。后面将会以例子中分析spring是从如何从底层让目标方法支持事务的。
4. 目标方法执行异常后的处理,通过TransactionAttribute的回调接口判断对于抛出的异常类型回滚还是继续提交。
- protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
- if (txInfo.transactionAttribute.rollbackOn(ex)) {
- getTransactionManager().rollback(txInfo.getTransactionStatus());
- }else{
- getTransactionManager().commit(txInfo.getTransactionStatus());}
- }
5. 在finally中进行清理工作,主要把老的TransactionInfo替换回去,使当前事务的上一个事务能够继续。
6.目标方法正常结束,则提交事务。
这其实是一个很通用事务处理逻辑过程,接下来我们具体分析PlatformTransactionManager如何创建事务,回滚及提交的。
2.4 事务管理器
PlatformTransactionManager抽象了事务管理的三个统一的入口方法
- public interface PlatformTransactionManager {
- //创建事务
- TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
- //提交事务
- void commit(TransactionStatus status) throws TransactionException;
- //回滚事务
- void rollback(TransactionStatus status) throws TransactionException;
- }
以DataSourceTransactionManager为例分析:
AbstractPlatformTransactionManager封装了采用模板模式规定了三个事务管理方法的基本逻辑,而将与具体事务资源相关的内容通过抽象方法开放给子类实现。具体说来主要包括以下几个方面:
1. 对于getTransaction()方法:
1.1 判断是否存在当前事务,然后根据当前事务存在与否进行不同的操作。
1.2 结合当前是否存在当前事务,然后根据根据传入的事务定义的传播行为执行后续的逻辑,如挂起当前事务,创建事务失败则恢复当前事务等。
2.对于commit()方法
在提交事务之前检查事务是否是readOnly,若是,则用回滚代替事务提交。
3.对于rollback() 方法,
在事务回滚后,清理并恢复事务的状态。
接下来从代码层面分析一下三个方法的大体逻辑
2.4.1 创建事务
创建事务的逻辑定义在AbstractPlatformTransactionManager类的getTransaction()方法中,中间省去一些相对不重要的代码(用//...注释代替)
- public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
- Object transaction = doGetTransaction(); //获取事务对象,这里是暴露给子类来提供。
- //... 获取日志是否是debug级别
- if (definition == null) {
- //没有传入事务定义信息,则采用默认配置。
- definition = new DefaultTransactionDefinition();
- }
- //如果存在当前事务,则根据事务传播行为,定义接下来事务的创建逻辑。
- if (isExistingTransaction(transaction)) {
- return handleExistingTransaction(definition, transaction, debugEnabled);
- }
- //不存在当前事务,则着手创建新的事务
- //事务超时时间配置错误,直接抛异常
- if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
- throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
- }
- //接着根据事务定义的传播行为创建事务
- // 传播行为为:PROPAGATION_MANDATORY,而当前事务不存在,很明显,只能抛异常了,其他情况,则可以继续
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
- throw new IllegalTransactionStateException(
- "No existing transaction found for transaction marked with propagation 'mandatory'");
- }
- else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
- definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
- definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- SuspendedResourcesHolder suspendedResources = suspend(null); //挂起事务
- try {
- doBegin(transaction, definition); //调用子类的模板方法,创建新事务
- }
- catch (TransactionException ex) {
- resume(null, suspendedResources); //抛出异常则,恢复挂起的事务
- throw ex;
- }
- boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); //判断是否需要同步
- return newTransactionStatus(
- definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
- }
- else {
- boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
- return newTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
- }
- }
针对于DataSourceTransactionManager的doGetTransaction()方法实现,从代码看,逻辑很简单:
- protected Object doGetTransaction() {
- DataSourceTransactionObject txObject = new DataSourceTransactionObject();
- txObject.setSavepointAllowed(isNestedTransactionAllowed());
- ConnectionHolder conHolder =
- (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
- txObject.setConnectionHolder(conHolder, false);
- return txObject;
- }
作为基于DataSource的事务管理,实际上数据库连接Connection是最为核心的资源,事务的管理控制最终都会由Connection的相关方法来完成。
isExistingTransaction()方法实际上是根据ConnectionHolder中Connection是否存在,并且是active来判断的。
根据这个状态分为两种情况
a. 当前事务存在
对于当前事务存在的情况,由handleExistingTransaction()方法统一处理,该方法主要是根据definition中指定事务传播行为和当前事务的关系进行处理。
在分析之前先了解spring中定义的传播行为,便于我们理解代码的逻辑:
- PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
- 前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
- 它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
- throw new IllegalTransactionStateException(
- "Existing transaction found for transaction marked with propagation 'never'");
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
- Object suspendedResources = suspend(transaction);
- boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
- return newTransactionStatus(
- definition, null, false, newSynchronization, debugEnabled, suspendedResources); //注意第二参数为null
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
- SuspendedResourcesHolder suspendedResources = suspend(transaction);
- try {
- doBegin(transaction, definition);
- }
- catch (TransactionException beginEx) {
- try {
- resume(transaction, suspendedResources);
- }
- catch (TransactionException resumeEx) {
- throw resumeEx;
- }
- throw beginEx;
- }
- boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(
- definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
- }
在DataSourceTransactionManager.doBegin()实现中,会判断当前事务没有绑定Connection,如果没有,则通过dataSource获取新的连接,然后会根据事务定义的属性,如隔离级别,超时时间设置到Connection中,并将Connection的自动提交设置为false(setAutoCommit(false)),最后会调用
- TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
对于传播行为是PROPAGATION_NESTED,即嵌套事务(嵌套事务是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. ),由定义可知,它会先检查底层事务资源是否支持savepoint,支持则创建在当前事务上创建一个TransactionStatus,并创建并保存嵌套事务开始的savepoint,便于后面回滚操作。对于不支持savepoint的资源,直接创建事务状态返回。
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- if (!isNestedTransactionAllowed()) {
- throw new NestedTransactionNotSupportedException(
- "Transaction manager does not allow nested transactions by default - " +
- "specify 'nestedTransactionAllowed' property with value 'true'");
- }
- if (useSavepointForNestedTransaction()) {
- // Create savepoint within existing Spring-managed transaction,
- // through the SavepointManager API implemented by TransactionStatus.
- // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
- DefaultTransactionStatus status =
- newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
- status.createAndHoldSavepoint();
- return status;
- }
- else {
- // Nested transaction through nested begin and commit/rollback calls.
- // Usually only for JTA: Spring synchronization might get activated here
- // in case of a pre-existing JTA transaction.
- doBegin(transaction, definition);
- boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
- }
- }
对于其他情况,都会直接构建TransactionStatus返回。
b. 当前事务不存在
对于当前事务不存在的情况 即isExistingTransaction()返回false的情况下。
在事务传播行为为PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED下,会先调用一个入参为null的suspend(null) 方法,参考suspend源码可以知道,这样做实际上为了给注册在TransactionSynchronizationManager中注册的TransactionSynchronization一个事件触发。
从TransactionSynchronization接口定义可以看到它主要关注以下事件:
- public interface TransactionSynchronization {
- void suspend();//事务挂起事件
- void resume();//事务恢复事件
- void beforeCommit(boolean readOnly);//事务提交之前
- void beforeCompletion();//事务提交或回滚之前
- void afterCommit();//事务提交之后
- void afterCompletion(int status);//事务提交或回滚完成后
- }
事务挂起的操作因具体的事务资源的不同而不同,因此具体的挂起逻辑交由子类实现,子类会将挂起的当前事务资源返回,最后被封装到SuspendedResourcesHolder中。若在在调用doBegin过程中出现异常,则通过resume()方法恢复被挂起的事务,这里还是会先给TransactionSynchronizationManager中注册的TransactionSynchronization一个事件触发,具体恢复工作交由子类实现。
2.4.2 事务回滚
rollback()方法的主要逻辑是在processRollback()中
- private void processRollback(DefaultTransactionStatus status) {
- try {
- try {
- triggerBeforeCompletion(status);
- if (status.hasSavepoint()) {
- status.rollbackToHeldSavepoint();
- }
- else if (status.isNewTransaction()) {
- doRollback(status);
- }
- else if (status.hasTransaction()) {
- if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
- doSetRollbackOnly(status);
- }
- }
- }
- catch (RuntimeException ex) {
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
- throw ex;
- }
- catch (Error err) {
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
- throw err;
- }
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
- }
- finally {
- cleanupAfterCompletion(status);
- }
- }
总结起来,对于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对象
- <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <property name="configLocation">
- <bean class="org.springframework.core.io.ClassPathResource">
- <constructor-arg><value>/sqlmap/sqlmap-auth-config.xml</value></constructor-arg>
- </bean>
- </property>
- <property name="dataSource">
- <ref local="dataSource" />
- </property>
- </bean>
SqlMapClientFactoryBean实现了FactoryBean和InitializingBean两个接口,它将创建sqlMapClient的逻辑放到了afterPropertiesSet()方法中。
- public void afterPropertiesSet() throws Exception {
- ... ...
- this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);
- // Tell the SqlMapClient to use the given DataSource, if any.
- if (this.dataSource != null) {
- TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
- DataSource dataSourceToUse = this.dataSource;
- if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
- dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
- }
- transactionConfig.setDataSource(dataSourceToUse);
- transactionConfig.initialize(this.transactionConfigProperties);
- applyTransactionConfig(this.sqlMapClient, transactionConfig);
- }
- ... ...
- }
首先通过buildSqlMapClient()创建sqlMapClient,这里面实际上就是通常的ibatis初始化工作,SqlMapconfigParser解析sqlmapconfig.xml配置文件,便可得到实例化后的sqlMapClient. 另外,SqlMapClientFactoryBean还支持直接指定sqlMap映射文件。至于如何解析ibatis配置文件及sqlMapClient的实例化过程,不在本篇讨论范围之内,后面会另外专门讨论。
得到sqlMapClient后,会检查是否指定了datasource属性,如果指定了就用指定的datasource设置到sqlMapClient中。这里并不会直接交给sqlmapClient一个原生的dataSource,而是动过手脚之后的。
首先,spring会用TransactionAwareDataSourceProxy把原生的数据源包裹起来,然后把它设置到ibatis的事务配置里面,当作事务的数据源。然后初始化事务配置:
- transactionConfig.initialize(this.transactionConfigProperties);
- this.transactionConfigProperties.setProperty("SetAutoCommitAllowed", "false");
最后用替换掉Ibatis本身的事务管理器,干净利落!
且来看看TransactionAwareDataSourceProxy做了些什么事情。
类结构图:
可以看出是典型的代理设计模式。
对于getConnection方法
- public Connection getConnection() throws SQLException {
- DataSource ds = getTargetDataSource();
- Assert.state(ds != null, "'targetDataSource' is required");
- return getTransactionAwareConnectionProxy(ds);
- }
- protected Connection getTransactionAwareConnectionProxy(DataSource targetDataSource) {
- return (Connection) Proxy.newProxyInstance(
- ConnectionProxy.class.getClassLoader(),
- new Class[] {ConnectionProxy.class},
- new TransactionAwareInvocationHandler(targetDataSource));
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //... ... 对于equals,hashCode等方法的代理
- else if (method.getName().equals("close")) {
- // Handle close method: only close if not within a transaction.
- DataSourceUtils.doReleaseConnection(this.target, this.targetDataSource);
- this.closed = true;
- return null;
- }
- if (this.target == null) {
- if (this.closed) {
- throw new SQLException("Connection handle already closed");
- }
- if (shouldObtainFixedConnection(this.targetDataSource)) {
- this.target = DataSourceUtils.doGetConnection(this.targetDataSource);
- }
- }
- Connection actualTarget = this.target;
- if (actualTarget == null) {
- actualTarget = DataSourceUtils.doGetConnection(this.targetDataSource);
- }
- if (method.getName().equals("getTargetConnection")) {
- // Handle getTargetConnection method: return underlying Connection.
- return actualTarget;
- }
- // Invoke method on target Connection.
- try {
- Object retVal = method.invoke(actualTarget, args);
- // If return value is a Statement, apply transaction timeout.
- // Applies to createStatement, prepareStatement, prepareCall.
- if (retVal instanceof Statement) {
- DataSourceUtils.applyTransactionTimeout((Statement) retVal, this.targetDataSource);
- }
- return retVal;
- }
- catch (InvocationTargetException ex) {
- throw ex.getTargetException();
- }
- finally {
- if (actualTarget != this.target) {
- DataSourceUtils.doReleaseConnection(actualTarget, this.targetDataSource);
- }
- }
- }
对于代理对象的close方法,直接调用DataSourceUtils.doReleaseConnection()方法释放连接,在DataSourceUtils.doReleaseConnection()方法中,实际上最后会通过TransactionSynchronizationManager来获取绑定到ThreadLocal中的ConnectionHolder,并释放。代码如下:
- if (dataSource != null) {
- ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
- if (conHolder != null && connectionEquals(conHolder, con)) {
- // It's the transactional Connection: Don't close it.
- conHolder.released();
- return;
- }
- }
同样,对于DataSourceUtils.doGetConnection(),最后也是通过TransactionSynchronizationManager.getResource()方法获取绑定到当前线程的Connection
在前面讲到过在事务创建阶段,DataSourceTransactionManager的方法doBegin()中会将当前事务相关的Connection绑定到当前线程:- TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
- TransactionSynchronizationManager.getResource(dataSource)
2.4.2 模板方法中的替换
对于基于IBatis的数据访问方式,Spring为我们提供了一个方便用于数据库访问的模板类SqlMapClientTemplate。
该类提供一个基于回调的模板方法
- public Object execute(SqlMapClientCallback action) throws DataAccessException {
- //... ...
- SqlMapSession session = this.sqlMapClient.openSession();
- if (logger.isDebugEnabled()) {
- logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
- }
- Connection ibatisCon = null;
- try {
- Connection springCon = null;
- DataSource dataSource = getDataSource();
- boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);
- // Obtain JDBC Connection to operate on...
- try {
- ibatisCon = session.getCurrentConnection();
- if (ibatisCon == null) {
- springCon = (transactionAware ?
- dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
- session.setUserConnection(springCon);
- }
- try {
- return action.doInSqlMapClient(session);
- }
- catch (SQLException ex) {
- throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
- }
- finally {
- try {
- if (springCon != null) {
- if (transactionAware) {
- springCon.close();
- }
- else {
- DataSourceUtils.doReleaseConnection(springCon, dataSource);
- }
- }
- }
- catch (Throwable ex) {
- }
- }
对于最后connection的释放,若dataSource是被代理的dataSource(TransactionAwareDataSourceProxy),则直接调用它的close方法,从前文中的分析中可以知道,该close方法最后会通过DataSourceUtils.doReleaseConnection()来释放连接。
通过多个方面的措施,最终spring就能保证目标DAO的操作都在Spring事务管理器的控制范围内。
3. 总结
万变不离其宗,对于基于DataSource的事务管理,最终的简化后的逻辑就是我们在 2.1节中总结原生jdbc事务管理方式。spring只是对原生方式进行各种抽象及封装,通过AOP,利用IOC提供的方便,最终可以让用户方便的使用事务管理的方式。