spring与ibatis集成之事务部分源码解析
Posted ze2200
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring与ibatis集成之事务部分源码解析相关的知识,希望对你有一定的参考价值。
ibatis是一个非常优秀的半自动ORM框架,相较于许多人认为编写sql和配置字段映射会降低开发效率,我认为在数据库最易成为系统瓶颈的情况下,开发人员必须通过手动编写sql来保证sql执行的高效,并在编写过程中思考表结构的设计是否合理。网上已有许多关于ibatis架构和映射实现原理解析的文章,本文主要讨论ibatis和spring集成后事务管理的实现。
在spring和ibatis集成后,有两种方式执行sql:第一种是dao继承spring的SqlMapClientDaoSupport,具体的sql通过调用父类的getSqlMapClientTemplate()得到的SqlMapClientTemplate实例执行;第二种同样是dao继承SqlMapClientDaoSupport,但具体的sql通过调用getSqlMapClient()得到的SqlMapClient实例执行,或不继承SqlMapClientDaoSupport,而是直接在dao注入SqlMapClient实例来执行sql。两种方式都可以正常执行sql,为什么我们要选择第一种呢?关键原因是第一种方式会将ibatis的事务委托给外部的spring管理,而第二种方式ibatis将自己管理事务,从SqlMapClient实例返回前将提交事务,最终导致spring无法完整地回滚事务。
源码分析基于 spring 3.0 + ibatis 2.3.0
ibatis事务管理相关对象类图
ibatis事务管理接口SqlMapTransactionManager的startTransaction&commitTransaction&endTransaction操作的实现,最终是调用TransactionManager的begin&commit&end操作实现。ibatis的事务仅针对单次执行的sql,也就是说调用两次SqlMapClient.update,本身是执行了两次事务。这样的事务管理在实际的应用中意义不大,但这是我们分析ibatis和spring集成后事务管理的基础,因此有必要先梳理清楚ibatis自带的事务管理。
ibatis事务操作时序图
如果要了解SqlMapClient及相关的SqlMapExecutorDelegate、TransactionManager实例是如何创建的,请阅读SqlMapClientFactoryBean的afterPropertiesSet()方法。从时序图可以了解到,SqlMapClient.update操作最终调用SqlMapExecutorDelegate.update操作(CRUD的实现都是这样),SqlMapExecutorDelegate.update方式实现了事务管理,代码解析如下图:
SqlMapExecutorDelegate.update(SessionScope session, String id, Object param) throws SQLException {
int rows = 0;
MappedStatement ms = getMappedStatement(id);
//从SessionScope实例获取Transaction实例。
//采用SqlMapClient.update执行CRUD操作,trans为空;
//采用spring的SqlMapClientTemplate.update执行CRUD操作,trans为UserProvidedTransaction实例,具体实现为
Transaction trans = getTransaction(session);
boolean autoStart = trans == null;
try {
//尝试开始事务
trans = autoStartTransaction(session, autoStart, trans);
...执行sql...(CUD操作会置SessionScope.commitRequired=true,以便后续提交事务)
//尝试提交事务:1、如果是批量操作且未执行,执行;2、如果SessionScope.commitRequired || forceCommit,提交事务
//3、置SessionScope.transactionState为TransactionState.STATE_COMMITTED
autoCommitTransaction(session, autoStart);
} finally {
//尝试结束事务:1、如果是批量操作,清理批量操作相关数据结构(Statment,语句List,结果List);
//2、如果SessionScope.transactionState!=TransactionState.STATE_COMMITTED(表示发生了异常),回滚事务;
//3、close Statement;TransactionManager执行中事务计数器减一;SessionScope.transaction = null,transactionState=TransactionState.STATE_ENDED
autoEndTransaction(session, autoStart);
}
return rows;
}
protected Transaction autoStartTransaction(SessionScope session, boolean autoStart, Transaction trans) throws SQLException {
Transaction transaction = trans;
if (autoStart) {
//最终调用TransactionManager.begin生成Transaction实例
//默认SessionScope.commitRequired=false,此属性会决定在调用事务提交操作时是否调用Connection.commit()操作
session.getSqlMapTxMgr().startTransaction();
transaction = getTransaction(session);
}
return transaction;
}
从上面的解释中,我们可以看到,ibatis是否启用事务自动管理的关键是SessionScope是否持有了Transaction实例。也就是说如果我们在代码执行到此处之前为SessionScope设置了Transaction实例,那么ibatis事务就可以委托给外部的spring管理(事务管理的关键,使用同一个Connection的实现逻辑将在后面提及,此处仅是分析将ibatis事务委托给spring管理的实现关键点)。
下面分析ibatis事务委托给spring管理的代码实现。分析前,有必要大致清楚spring的事务管理是如何实现的(仅仅是帮助理解,不会细致分析spring的事务实现)。spring有两种事务配置方式,声明式和编程式,两种方式对应的事务管理代码实现是在TranactionInterceptor.invoke()(spring4是调用父类的invokeWithinTransaction方法)方法。
spring事务管理的时序图:
代码解析如下,基于事务传播属性为:PROPAGATION_REQUIRED:
TranactionInterceptor.invoke(final MethodInvocation invocation) {
...
//获取用户定义的事务传播属性,和事务隔离级别
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
...
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//1、生成Transaction实例;2、DataSource获取Connection,设置到Transaction,并将Connection绑定到线程变量,本次事务内
// 调用DataSourceUtils.getConnection(DataSource) 获取到的是同一个Connnection实例;
//3、生成TransactionInfo实例(持有事务管理所需的全部实例),绑定到线程变量
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
try {
//执行具体的应用代码,最终执行到dao
retVal = invocation.proceed();
} catch (Throwable ex) {
//回滚事务。有兴趣可自行阅读
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
//恢复线程变量持有的原TransactionInfo实例
cleanupTransactionInfo(txInfo);
}
//提交事务,清除线程变量持有的Connection,恢复Connection的autoCommit、
//transactionIsolation、readOnly等属性,将Connection返回连接池
commitTransactionAfterReturning(txInfo);
return retVal;
} else {
// CallbackPreferringPlatformTransactionManager的事务执行逻辑,将事务管理委托给了j2ee容
//器提供商提供的TransactionManager,websphere的如WebSphereUowTransactionManager.
...
}
}
spring开启事务管理的代码解析:
//如果需要,创建事务。什么情况下不需要,请结合spring的7个事务传播属性确定
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
...
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
//调用配置的TransactionManager生成Transaction对象并开启事务
status = tm.getTransaction(txAttr);
}
}
//设置TransactionInfo实例,并绑定到线程变量
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
//调用PlatformTransactionManager实例(本文配置为DataSourceTransactionManager)的getTransaction()操作获取事务,并开启事务,如果有必要。
//模板模式实现,AbstractPlatformTransactionManager.getTransaction()实现了主流程
AbstractPlatformTransactionManager.getTransaction(TransactionDefinition definition) {
//调用配置的事务管理器DataSourceTransactionManager.doGetTransaction()生成事务实例
Object transaction = doGetTransaction();
...
if (isExistingTransaction(transaction)) {
//嵌套事务下,spring事务传播属性语义的实现逻辑
return handleExistingTransaction(definition, transaction, debugEnabled);
}
...
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) {
//系统将执行本段代码,本文不涉及嵌套事务和事务传播行为各种组合的分析
...
try {
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//调用具体的事务管理器DataSourceTransactionManager开启事务
doBegin(transaction, definition);
//设置TransactionSynchronizationManager,spring事务管理核心属性管理器
prepareSynchronization(status, definition);
return status;
} catch (RuntimeException ex) {}
} else {
//事务传播属性中,不需要事务行为的逻辑分支
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
//开始事务
DataSourceTransactionManager.doBegin(Object transaction, TransactionDefinition definition) {
...
try {
//无法从事务实例中获取Connection
if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//从DataSource获取Connection实例
Connection newCon = this.dataSource.getConnection();
...
}
...设置Connnection的transactionIsolation等属性
if (txObject.isNewConnectionHolder()) {
//绑定Connection到线程变量,本事务内使用DataSourceUtils.getConnection获取的是同一个Connection
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
} catch (SQLException ex) {...}
}
spring开启事务管理,绑定了Connection到线程变量,所有将事务委托给spring管理的代码,调用DataSourceUtils.getConnection(),获取到的是同一个Connection实例,这是事务实现的关键。下面是ibatis将事务委托给spring管理的代码实现分析:
SqlMapClientDaoSupport.getSqlMapTemplate().update(final String statementName, final Object parameterObject) {
return execute(new SqlMapClientCallback<Integer>() {
public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
return executor.update(statementName, parameterObject);
}
});
}
SqlMapClientTemplate.execute(SqlMapClientCallback<T> action) {
//新建SqlMapSessionImpl实例
SqlMapSession session = this.sqlMapClient.openSession();
try {
...
//dataSource是否为TransactionAwareDataSourceProxy实例的逻辑,请参考
//SqlMapClientFactoryBean.afterPropertiesSet,由useTransactionAwareDataSource控制
//此处值为false;如果transactionAware==true,将导致spring无法管理本次sql执行事务
boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);
try {
//ibatisCon为空
ibatisCon = session.getCurrentConnection();
if (ibatisCon == null) {
//调用DataSourceUtils.doGetConnection(dataSource)获取spring事务开始时绑定到线程变量的Connection实例
springCon = transactionAware ? dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource);
//最终调用SqlMapExectorDelegate.setUserProvidedTransaction()
//1,生成UserProvidedTransaction实例;2,将springCon设置到事务实例;3,将事务实例设置到session变量持有的SessionScope实例
//前面的分析中已经提到,ibatis的事务管理会检查SessionScope是否持有的Transaction,如果持有,
//就不开启自带的事务管理,故setUserConnection操作实现了将ibatis事务委托给外部管理的功能。
session.setUserConnection(springCon);
}
...
} catch() {}
try {
//执行ibatis CRUD操作
return action.doInSqlMapClient(session);
} catch (SQLException ex) {
} finally {
...
//将ConnectionHolder中,对Connection使用的计数器减1
DataSourceUtils.doReleaseConnection(springCon, dataSource);
}
} finally {}
}
从以上啰嗦的分析中可以看出,spring和ibatis的事务整合实现逻辑并不复杂,只要理解了ibatis是如何将事务委托给外部管理的设计,和事务的sql操作必须同一个Connection的原则,理解SqlMapClientTemplate相关的事务整合代码就没有任何问题。框架极大降低了开发难度的同时,也屏蔽了许多我们应该掌握的技术细节,学习框架的实现方案,了解技术细节,对实际的应用开发是非常有益的。
最后提供一个调试ibatis源码的笨方法:在你的工程里新建一个ibatis工程,导入源码,添加依赖的jar,将你的dao工程依赖的ibatis改为新建的ibatis工程,就可以调试源码啦...
以上是关于spring与ibatis集成之事务部分源码解析的主要内容,如果未能解决你的问题,请参考以下文章
2RocketMQ 源码解析之 与 Spring Boot 集成
2RocketMQ 源码解析之 与 Spring Boot 集成
1Nacos 配置中心源码解析之 集成 Spring Cloud