接上文获取mapper
接上文的MapperMethod,看看他的execute,这里挑一个简单的Insert看看:
case INSERT: {
// 这一句是获取参数吧?
Object param = method.convertArgsToSqlCommandParam(args);
// 执行并返回结果
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
先看参数吧,method?哦,内部类MethodSignature,看调用的啥:
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
纳尼?paramNameResolver?参数名解析器?这是啥,找找构造方法看看呢,就在上面:
this.paramNameResolver = new ParamNameResolver(configuration, method);
有必要看看ParamNameResolver这个解析器及其相关方法getNamedParams:
/**
* <p>
* A single non-special parameter is returned without a name.<br />
* Multiple parameters are named using the naming rule.<br />
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
* </p>
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 只有一个参数,就直接返回了
return args[names.firstKey()];
} else {
// 关键代码,如果参数不是空且大于一个,最终返回的是这货,是个map,是个map,是个map,重三
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
注释中重要的代码说三遍,使用中纠结过这个问题,再次强调:无参返null,单参直接返,多参返个map,map,map!!! 解析的细节先不管,继续追主干,拿到参数后怎么干,调用了rowCountResult,并且参数是执行sqlSession.insert的结果,那个挨个儿看吧:
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
内部调用了update方法(实际上执行器内部只有query和update,增删改全都是update),再看,又出现了一个MappedStatement,还是通过配置对象获取的,最后还是交给了Executor执行器执行update方法; 继续看:
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
最后只是简单的调用mappedStatements.get(id)并返回,那么mappedStatements里的值是哪来的?看来关键就在于中间的buildAllStatements方法了:
/*
* Parses all the unprocessed statement nodes in the cache. It is recommended
* to call this method once all the mappers are added as it provides fail-fast
* statement validation.
*/
protected void buildAllStatements() {
if (!incompleteResultMaps.isEmpty()) {
synchronized (incompleteResultMaps) {
// This always throws a BuilderException.
incompleteResultMaps.iterator().next().resolve();
}
}
if (!incompleteCacheRefs.isEmpty()) {
synchronized (incompleteCacheRefs) {
// This always throws a BuilderException.
incompleteCacheRefs.iterator().next().resolveCacheRef();
}
}
if (!incompleteStatements.isEmpty()) {
synchronized (incompleteStatements) {
// This always throws a BuilderException.
incompleteStatements.iterator().next().parseStatementNode();
}
}
if (!incompleteMethods.isEmpty()) {
synchronized (incompleteMethods) {
// This always throws a BuilderException.
incompleteMethods.iterator().next().resolve();
}
}
}
鸟语不好的孩子伤不起,明明人家有注释的... ... 这里关心statement,暂且就只看这一部分吧,此处是调用了这个方法parseStatementNode():
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
好长好看,顾名思义,解析statement节点,这里先忽略其它,我们看到最后一句有一个builderAssistant,类型是MapperBuilderAssistant,构建助手,调用这个助手的一个有好多好多好多参数的addMappedStatement方法,看一下:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 发现一个熟悉的元素,一个构建器
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
// 参数不是空的,就设置参数
statementBuilder.parameterMap(statementParameterMap);
}
// 构建出MappedStatement
MappedStatement statement = statementBuilder.build();
// 设置到configuration
configuration.addMappedStatement(statement);
return statement;
}
又是长的吓人,一点点找熟悉的元素吧,找到一个眼熟的,创建了一个构建器:MappedStatement.Builder,还给他设置了参数,之后调用build()方法构建出我们要找的MappedStatement ,然后configuration.addMappedStatement(statement); 明了了,继续跟吧:
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
这里最终添加到了mappedStatements,圆起来了,在上面我们就通过mappedStatements.get(id);返回 的MappedStatement,转了一大圈儿,应该能看出来,MappedStatement这个东西:
- 它保存映射器的一个节点,包含我们配置 的SQL,SQL的ID,缓存信息,resultMap,resultType等 重要信息;
- 参数中有一个关键:SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
- SqlSource是提供BoundSql对象的地方,主要作用是根据参数和其他规则组装SQL
- 而BoundSql,是建立SQL和参数的地方,包含属性:String sql、List<ParameterMapping> parameterMappings、Object parameterObject;
有了MappedStatement对象,再加上经过wrapCollection处理的parameter,就可以调用执行器的update方法 // TODO
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// update方法,要使用查询 缓存失效
clearLocalCache();
return doUpdate(ms, parameter);
}
最后调用了模板方法doUpdate,该方法由子类实现,前面已经跟踪过,我们的默认执行器是SimpleExecutor,那么看看他的doUpdate方法:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//又一个关键对象,插件可拦截对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
最终的执行委托给了StatementHandler ,也是一个可拦截对象,看看这个对象的创建,回到了configuration:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 熟悉的操作,依次代理,在创建执行器时已经调用过
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
创建的实例是RoutingStatementHandler,实际在这里面又根据statementType不同分成几个,真正的行为会委托给内部持有的 StatementHandler delegate,:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
通常真正执行动作的是mybatis默认的类型PreparedStatementHandler,预编译statement处理器; 这样子,我们最终的update执行地儿:
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
浓浓的我们熟悉的JDBC元素,执行完后还有几步,用于如果插入语句,可能需要获取自增ID;
整个流程就结束了,还有些细节给略过了,但大致的流程是没错的,如果是查询语句,会有点不一样,因为要处理返回映射:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 调用结果处理器将结果转换成我们指定的类型返回
return resultSetHandler.<E> handleResultSets(ps);
}
这里出现了最后一个重要的元素:ResultSetHandler,结果处理器,顾名思义,处理返回结果的,也是一个可拦截对象;简单看一下:
//
// HANDLE RESULT SETS
//
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
这里简单看一下,细节暂时不深究了,无非是把ResultSet中的数据取出来,按规则处理成我们要的返回 类型,实现还是蛮复杂的,内容已经太多了,本文 只看执行主干流程,至于参数的处理细节、结果处理细节等 等,都先不考虑;
这只是单独使用mybatis时的过程,没考虑跟IOC框架等 结合使用时的场景 ,这种场景下某些组件要另外实现,如SqlSession/Mapper等 ,都需要给出一个线程安全的实例 ;
多数情况下,会配置spring实现,相当的数据源、事务处理等 都要换成spring中的实现,而其他组件如SessionFactory等 要换成mybatis-spring扩展包中的实现;
但原理是一样的;