前言
之前在用到Mybatis插件的时候就有提到过要好好分析下Mybatis的四大组件,但是由于之前一直在弄公司的消息服务器集群搭建的事情,就耽搁了好一阵子。现在终于有时间了,我们就来好好看看Mybatis的源码。
从一次查询过程说起
上一次我们我们讲述了Mapper接口以及命令模式的具体命令类MapperMethod。
我们接着之前的分析继续往下走这个查询过程。
接下来就该来到DefaultSqlSession类了。
SqlSession.selectList接MapperMethod
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.从confguration(构造DefaultSQLSession使用了建造者模式,configuration对象就是实际具体的建造者)对象中得到MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//2.使用执行器来进行查询的工作,现在我们的重心就到了Executor执行器上了
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
上述分析,我们得知了,真正的查询操作是通过Executor执行器代劳的。
Executor
在上述代码中,我们可以知道Executor对象是DefaultSqlSession类中的一个属性,那么首先,我们就得知道Executor是怎么初始化的。
我们从得到DefatulSqlSession的
SqlSession session= sessionFactory.openSession();
这行代码为入口分析
sessionFactory.openSession();
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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);
//1、在这里初始化了executor
final Executor executor = configuration.newExecutor(tx, execType);
//2、并将executor作为DefaultSqlSession构造方法的实参传入,Executor和SqlSession的联系就是在这里生成的。
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();
}
}
- configuration.newExecutor(tx, execType);
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 {
//如果没有显式的设置,则使用SimpleExecutor,一般都是使用这个Executor
executor = new SimpleExecutor(this, transaction);
}
//如果是缓存了的执行器,就新建缓存专用的执行器。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
该方法通过Mybatis相关配置文件中设置的ExecutorType来决定(如果没有显式的设置,则使用默认的Type)到底生成哪种Executor。
这里对几种Executor进行介绍一下,并说明用使用显式配置时该如何配置
SimpleExecutor – SIMPLE 就是普通的执行器。
ReuseExecutor -执行器会重用预处理语句(prepared statements)
BatchExecutor –它是批量执行器
这些就是mybatis的三种执行器。你可以通过配置文件的settings里面的元素defaultExecutorType,配置它,默认是采用SimpleExecutor
<setting name="defaultExecutorType" value="SIMPLE"/>
如果你在Spring运用它,那么你可以这么配置它:
<bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<!--更新采用批量的executor -->
<constructor-arg index="1" value="BATCH"/>
</bean>
继续分析查询过程
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.从confguration(构造DefaultSQLSession使用了建造者模式,configuration对象就是实际具体的建造者)对象中得到MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//2.使用执行器来进行查询的工作,现在我们的重心就到了Executor执行器上了
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- 1.warpCollection是对Method参数的又一次处理,我们看看具体做了些什么
private Object wrapCollection(final Object object) {
//我们看到如果经过MapperMethod封装的参数的类型是List类型的话
if (object instanceof List) {
StrictMap<Object> map = new StrictMap<Object>();
//将object作为Value存入map中,而且Key为list,StrictMap继承了HashMap,并且该HashMap的Key是String类型的,这一层封装的意义就在于限定该map的Key只能为String类型
//这里更需要引起我们注意的是这里的Key为list,当你第一眼看到这个list的时候会想到什么呢,
//反正我是一下就想到了在使用动态sql的foreach节点时候出现过list,所以,为了能够对Mapper配置文件中的list对应的对象群出来,这里先将list设置好,这就为什么当参数是List类型时候放入Map时的Key设为list了。
map.put("list", object);
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
//当数组类型时,放入map中,Key为array,理由同上
map.put("array", object);
return map;
}
return object;
}
warpCollection这个方法的作用就是:
将List或者Array类型转化为Map类型的,并且存入Map时一定要给限定相应的Key,至于为什么要转为Map类型的,当我们分析到后面的时候自然就会有答案了,现在不用深究。
public static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -5741767162221585340L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
}
return super.get(key);
}
}
}
- 2.query方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//1. BoundSql是对sql的封装,是MappedStatement对Mapper配置文件提取后将SQL的相关信息封装后都集中了BoundSql中,具体是怎么集中封装的,下面会分析
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//2
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
- 1.MappedStatement的getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
//2 DynamicSqlSource的getBoundSql
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//将得到的paramterMappings放入到BoundSql对象中,这点是非常重要的
if (parameterMappings == null || parameterMappings.size() <= 0) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
-
- 2.DynamicSqlSource的getBoundSql(因为使用了动态SQL,所以使用的DynamicSqlSource,关于各种情况具体是怎么封装sql的,也就是关于BoundSql对象以及SqlSource接口和相应的一些实现类,会有一个专门的博客介绍,对于Mybatis源码的一些关键类,都应该有单独的博客介绍,但是我们还是先有一个大体的认识)
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
//3.将Mapper配置文件中的SQL组装起来,在初始化的过程中,该SQL对应的处理节点分成了两个StaticSqlNode,和一个ForEachSqlNode。
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//4.得到SqlSource对象,最重要的是parse方法让我们得到paramtermappings,这个对象对我们执行参数化sql时非常重要
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
-
- 3.DynamicContext的创建
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
public DynamicContext(Configuration configuration, Object parameterObject) {
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
这里没有特别难理解的地方,就是DynamicContext中有个ContextMap类型(同样继承了HashMap)的属性bindings,这个属性是非常重要的,然后将两个元素放入这个Map中,这里需要注意的是我们会将Method封装参数放入Map中,并且对应的Key是_parameter。一看到_parameter,我们肯定就想到了在学习编写Mapper配置文件中使用过这个。
3.(1)MixedSqlNode
private List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
这里的contents就对应的两个StaticSqlNode和一个ForEachSqlNode,如何拼接ForEachSqlNode,以及如何提取ps.setParameter()所用到的信息是问题的关键。
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
if (first) {
context = new PrefixedContext(context, "");
} else {
if (separator != null) {
context = new PrefixedContext(context, separator);
} else {
context = new PrefixedContext(context, "");
}
}
int uniqueNumber = context.getUniqueNumber();
if (o instanceof Map.Entry) { // Issue #709
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
applyIndex(context, i, uniqueNumber);
applyItem(context, o, uniqueNumber);
}
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) first = !((PrefixedContext) context).isPrefixApplied();
context = oldContext;
i++;
}
applyClose(context);
return true;
}
-
- 4.最后我们需要了解下parse方法,注意这个方法的具体实现里面得到了paramterMappings,这是非常重要的。
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
//在这个过程中会把 #{} 里的信息赋给paramterMappings
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
- 2.BaseExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//生成一个CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
终于分析到这个方法的最后一行代码了。
BaseExecutor的query方法
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//查看是不是已经有查询过了,有的话直接在本地缓存中取出来
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//如果是存储过程,需要把输出处理一下,具体处理目前没有细看
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果没有本地缓存或者使用了resultHandler,需要到数据库中去查。
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//在这里初始化了除了Excutor的其他三大组件,注意了,这行代码完成的工作很重要,而Executor对象则在初始化得到DefaultSqlSession对象的时候就已经注入到其中了。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在这个方法里面,我们完成了得到结果的所有流程。
1、我们先得到Configuration对象。
2、利用COnfiguration对象去生成其他三大组件对象,用三大组件对象完成查询的具体操作。
3、利用三大组件进行具体得到结果