今天,我拟从一个简单的selectOne查询入手,追踪mybatis框架执行的足迹。
@Test
public void queryFinancialAccountTest(){
FundFinancialExtDTO financialAccountExtPO = new FundFinancialExtDTO();
// 部分代码略去
FundAccAndExtDTO fundAccAndExtDTO = fundFinancialExtMapper.queryFinancialAccount(financialAccountExtPO);
}
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.roger.practice.dal.dao" />
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.roger.practice.entity" />
<property name="mapperLocations" value="classpath*:mapper/**/*.xml" />
<property name="plugins">
<array>
<bean class="com.roger.practice.mybatis.page.interceptor.PageInterceptor" />
<bean class="com.roger.practice.mybatis.page.interceptor.PageSqlRewriteInterceptor">
<property name="dialect" value="oracle" />
bean>
array>
property>
bean>
Mybatis集成在Spring中,方法afterPropertiesSet()将在所有的属性被初始化后被调用。查看org.mybatis.spring.SqlSessionFactoryBean类中的afterPropertiesSet()方法,发现该方法开始创建SqlSessionFactory实例。
@Override
public void afterPropertiesSet() throws Exception {
// 略去代码请参考源码
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// 略去代码将对象工厂objectFactory,对象包装工厂objectWrapperFactory,类型别名typeAliasesPackage/typeAliases,插件plugins,类型处理器typeHandlersPackage/typeHandlers,缓存cache,环境environments,事务工厂transactionFactory等信息配置存到Configuration对象中。
// 通过xmlMapperBuilder来解析mapper文件
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
org.apache.ibatis.session.SqlSessionFactoryBuilder提供了9种构造SqlSessionFactory的方法,但最终都要调用包含Configuration对象的构造方法,其通过加载配置文件构造SqlSessionFactory对象、返回DefaultSqlSessionFactory对象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
org.apache.ibatis.binding.MapperProxyFactory
Spring负责创建SqlSessionTemplate,执行getMapper方法时会创建动态代理,代理Test用例中的FundFinancialExtMapper接口。
@Override
public T getMapper(Class type) {
return getConfiguration().getMapper(type, this);
}
org.apache.ibatis.binding.MapperProxy
Mybatis初始化加载的时候,利用MapperProxy代理了自己的Mapper接口类,生成一个代理处理类。代理处理的逻辑都在invoke方法里,它根据目标类的接口(本例是FundFinancialExtMapper)生成 MapperMethod。sqlSession是由spring负责生成的SqlSessionTemplate,它是spring连接mybatis的模板类。接下来调用MapperMethod的execute方法就能获取执行结果。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
org.apache.ibatis.binding.MapperMethod
MapperMethod就像是一个分发者,它根据SqlCommandType,并获取执行参数commandName和param,交由SqlSessionTemplate对象执行具体的操作。这样mapper对象与sqlSession就真正的关联起来了。本例中,将执行SqlSessionTemplate类中的selectOne方法。
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
org.mybatis.spring.SqlSessionTemplate
SqlSessionTemplate的实际执行是交给它的代理类完成的。查看SqlSessionTemplate构造函数可知,它是由内部类SqlSessionInterceptor动态代理的,所有的处理逻辑都是在invoke方法里。invoke方法里执行了:
1. 通过静态方法SqlSessionUtils.getSqlSession创建sqlSession,实际返回DefaultSqlSession对象。
2. DefaultSqlSession执行selectOne方法。
3. 执行成功则提交,出现异常则关闭sqlSession。
// SqlSessionTemplate的构造函数
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
// 内部类SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 略去代码请参考源码
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
org.apache.ibatis.executor.Executor
sqlSession只是一个门面,真正发挥作用的是executor,对sqlSession方法的访问最终都会落到executor的相应方法上去。
executor对象是执行openSessionFromDataSource方法时创建的,见org.apache.ibatis.session.Configuration里的newExecutor方法。executor具体实现是SimpleExecutor,由于cacheEnabled默认为ture,还追加了缓存功能。另外它还可以追加拦截器。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
// 实现缓存
executor = new CachingExecutor(executor);
}
// 追加拦截器,比如分页拦截器
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
org.apache.ibatis.executor.CachingExecutor
query方法会最终委派org.apache.ibatis.executor.SimpleExecutor类中的doQuery方法。query方法如下:
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
org.apache.ibatis.executor.SimpleExecutor
SimpleExecutor的doQuery方法是具体的实现。
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// StatementHandler的创建与executor的实现很相似,又是在Configuration里通过newStatementHandler方法创建的。
// 由org.apache.ibatis.executor.statement.RoutingStatementHandler代理实际的StatementHandler实现。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 对查询语句进行预编译,并解析参数实体。
stmt = prepareStatement(handler, ms.getStatementLog());
// 方法执行时改由PreparedStatementHandler实际代理进行查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
通过StatementType获取具体的StatementHandler类,从默认配置可知,PreparedStatementHandler是实际代理的对象。其中构造StatementHandler的时候,查看BaseStatementHandler构造函数可知,ParameterHandler和ResultSetHandler也是有Configuration生成的,同样也可追加拦截器。
org.apache.ibatis.executor.statement.PreparedStatementHandler
PreparedStatementHandler的父类是BaseStatementHandler,BaseStatementHandler的构造函数是有这么一段:
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等。
query方法如下,它执行了execute方法并完成结果集的映射。
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 实际执行在此处那!!!
ps.execute();
// 由org.apache.ibatis.executor.resultset.DefaultResultSetHandler代理实现
// 完成结果集的映射
return resultSetHandler. handleResultSets(ps);
}
org.apache.ibatis.scripting.defaults.DefaultParameterHandler
setParameters方法用来解析参数实体,其中propertyName获取参数名,value是参数值,typeHandler和jdbcType是参数类型。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
完成结果集的映射。
org.apache.ibatis.session.SqlSessionFactory
SqlSessionFactory作为SqlSession的工厂,提供了8种获取SqlSession的方法,同时还提供了获取Configuration的方法。
8种获取SqlSession的方法主要涉及4个参数:是否自动提交、自定义Connection、事务级别、ExecutorType(Statement类型【普通、预处理、批处理】)。包含Connection类型参数的方法会调用openSessionFromConnection方法,其它都会调用openSessionFromDataSource方法,最终都返回DefaultSqlSession对象。
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory是SqlSessionFactory接口的实现,以openSessionFromDataSource方法为例,创建sqlsession经过了以下几个主要步骤:
1) 从配置中获取Environment;
2) 根据Environment创建事务工厂TransactionFactory;
3) 从Environment中取得DataSource、进而创建事务对象Transaction;
4) 创建Executor对象;
5) 创建sqlsession对象。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
org.apache.ibatis.session.SqlSession
org.apache.ibatis.session.defaults.DefaultSqlSession
DefaultSqlSession实现了SqlSession接口,主要封装了Configuration对象、Executor对象、是否自动提交。
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
// 默认不自动提交
this(configuration, executor, false);
}
它利用自己封装的一套东西,还包括Executor(封装Statement)、ResultHandler(封装处理ResultSet对象)、RowBounds(封装分页对象),提供了CRUD、提供了缓存机制、提供了根据配置文件获取Sql语句的方法,提供了事务的提交和回滚等。
大体流程就是:
1. 加载XML配置文件创建Configuration对象完成初始化,创建并使用SqlSessionFactory对象。
2. 利用MapperProxy代理具体的Mapper接口类,生成了MapperMethod。
3. Spring负责生成SqlSessionTemplate,它实际由SqlSessionInterceptor动态代理,所有的处理逻辑都是在 invoke方法里,主要是获取SqlSession、生成可带缓存可追加插件的Executor,并执行操作。DefaultSqlSession由SimpleExecutor静态代理执行查询操作。
4. RoutingStatementHandler根据配置Statement类型创建真正执行数据库操作的StatementHandler,实际由PreparedStatementHandler进行查询语句的预编译、查询参数实体解析、执行查询。
5. DefaultResultSetHandler完成结果集的映射。