记一次解决问题过程
缘起
由于公司多数中间件基于ibatis2
,并且在sql方面基本全部动态化,在开发阶段基本不可能知道sql最终的形态,这对测试的准确性带来了一定的困难,所以就有了这么一个Action
, 对ibatis2
测试环境进行扩展,在重要的测试场景能够等到最终运行的sql,用来与预期的sql进行比较。
思路
拿到问题大致看了一下ibatis2
源码发现sql语句在MappedStatement
可以透出,这里具体怎么通过MappedStatement
得到就不深究了,这里主要记录一下解决问题的思路。那么有了这个结论问题就很明确了,我们主要拿到MappedStatement
就可以了!带着这个问题我们继续从ibatis2
入口深入,发现基本调用流程如下:
SqlMapClientFactoryBean->SqlMapClient->SqlMapSession->SqlMapExecutorDelegate ;所有的类似insert、update、delete...执行最终都会落到
SqlMapExecutorDelegate
然而在 SqlMapExecutorDelegate
提供了 getMappedStatement(String id)
方法可以拿到我们需要的MappedStatement
。接着我对SqlMapClientFactoryBean
(一个Spring bean工厂)进行分析,发现下面这段代码:
//spring InitializingBean 在bean创建后的处理
public void afterPropertiesSet() throws Exception {
if (this.lobHandler != null) {
configTimeLobHandlerHolder.set(this.lobHandler);
}
try {
// 非常关键的方法, 这里创建了 sqlMapClient,方法内容看下面。
this.sqlMapClient = this.buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);
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((DataSource)dataSourceToUse);
transactionConfig.initialize(this.transactionConfigProperties);
this.applyTransactionConfig(this.sqlMapClient, transactionConfig);
}
} finally {
if (this.lobHandler != null) {
configTimeLobHandlerHolder.remove();
}
}
}
protected SqlMapClient buildSqlMapClient(Resource[] configLocations, Resource[] mappingLocations, Properties properties) throws IOException {
if (ObjectUtils.isEmpty(configLocations)) {
throw new IllegalArgumentException("At least 1 'configLocation' entry is required");
} else {
SqlMapClient client = null;
SqlMapConfigParser configParser = new SqlMapConfigParser();
Resource[] var9 = configLocations;
int var8 = configLocations.length;
for(int var7 = 0; var7 < var8; ++var7) {
Resource configLocation = var9[var7];
InputStream is = configLocation.getInputStream();
try {
client = configParser.parse(is, properties);
} catch (RuntimeException var13) {
throw new NestedIOException("Failed to parse config resource: " + configLocation, var13.getCause());
}
}
if (mappingLocations != null) {
SqlMapParser mapParser = SqlMapClientFactoryBean.SqlMapParserFactory.createSqlMapParser(configParser);
Resource[] var14 = mappingLocations;
int var17 = mappingLocations.length;
for(var8 = 0; var8 < var17; ++var8) {
Resource mappingLocation = var14[var8];
try {
mapParser.parse(mappingLocation.getInputStream());
} catch (NodeletException var12) {
throw new NestedIOException("Failed to parse mapping resource: " + mappingLocation, var12);
}
}
}
return client;
}
}
从这段代码我们明确了SqlMapClient
的产出,而SqlMapClient
持有SqlMapExecutorDelegate
和SqlMapSession
,下面我们就看看这些个对象的关系:
public class SqlMapClientImpl implements SqlMapClient, ExtendedSqlMapClient {
private static final Log log = LogFactory.getLog(SqlMapClientImpl.class);
public SqlMapExecutorDelegate delegate;
protected ThreadLocal localSqlMapSession = new ThreadLocal();
public SqlMapClientImpl(SqlMapExecutorDelegate delegate) {
this.delegate = delegate;
}
}
看到这里估计大家思路应该就清楚了吧,我如果让SqlMapClientFactoryBean
返回我自定义的SqlMapClient
,我自定义的SqlMapClient
使用我自定义的SqlMapExecutorDelegate
,这样我就可以对SqlMapExecutorDelegate
进行包装了,比如给insert、update、query这一类方法添加前置、后置处理,进而达到我们的目的了。
最后
最后呢,在这里贴一下源码,有兴趣可以看看,不过这种老版本的ibatis感觉用的人也比较少了。