springboot的MybatisAutoConfiguration自动配置类会创建SqlSessionFactory,创建过程就是填充configuration属性,调用buildSqlSessionFactory()方法完成SqlSessionFactory创建,这其中就会创建XMLMapperBuilder解析mapper.xml和XMLConfigBuilder解析mabatis-config.xml
关键方法parse
这个方法就是解析mapper中的所有标签,封装到configuration的属性中。然后构建mapper与xml的关系,后面查询时方便调用。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//处理<mapper >节点 也就是解析所有xml中的所有标签 例如select resultmap
configurationElement(parser.evalNode("/mapper"));
//将 resource 添加到 Configuration.loadedResources 集合 保存 它是 HashSet<String>
//类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
//2.构建mapper与xml的关系 通过xml里定义的namespace
bindMapperForNamespace();
}
//处理解析失败的<resultMap>标签
parsePendingResultMaps();
parsePendingCacheRefs();
//处理解析失败的SQL语句
parsePendingStatements();
}
parse方法是被循环调用的,也就是每个mapper.xml都会执行一次。
关键方法:configurationElement
这里会解析xml中的每个标签和sql语句
重要的有resultMap、sql、select、delete、update、insert
关键方法buildStatementFromContext方法
这个方法会解析select delete等标签以及内部的where if等标签,如果有where这样的标签 则会封装成DynamicSqlSource,如果是普通的查询语句(select * from departments where department_id=#{depId})则会被解析成RawSqlSource,这个属性会被存在configuration的mappedstatements属性中,属性名称为sqlSource。
关键方法resultMapElements方法,解析resultMap标签。
1. 解析resultMap标签
解析完放到Configuration的resultMaps属性中,一种是全限定名的形式,一种是标签名的形式(兼容老版本?)。
XMLMapperBuilder类的resultMapElements方法
就是获取到resultMap的各种属性,然后调用ResultMapResolver 的resolve方法继续处理
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//拿到reusltMap标签上的type属性的值
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
//拿到子标签 也就是resultMap中映射的所有字段
List<XNode> resultChildren = resultMapNode.getChildren();
//遍历
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
//判断id标签
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//添加到resultMappings中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//获取到resultMap的id属性的值
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//构建一个ResultMapResolver 传入上面获取到的type id resultMappings
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//调用resolve方法,给configuration中的resultMaps属性赋值
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
ResultMapResolver 的resolve方法
继续调用本类的MapperBuilderAssistant的addResultMap方法
MapperBuilderAssistant类的addResultMap方法
构建resultMap添加到Configuration中
这里用处就是在构建MappedStatement.Builder时从这个Confiuration中拿到resultMap,后面查询完解析结果时是从mappedStatement中拿到的resultMap。
解析完的resultMap
2. 解析select等标签
XMLMapperBuilder类的buildStatementFromContext方法
传入所有标签,遍历每个标签构建XMLStatementBuilder进行解析
XMLStatementBuilder的parseStatementNode方法
public void parseStatementNode() {
//获取标签的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//拿到nodeName 也就是select delete等
String nodeName = context.getNode().getNodeName();
//sql类型 也就是select delete等
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());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//构建sqlsource 关键方法 解析sql的 处理#{} where标签等等
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
//resultMap
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//调用MapperBuilderAssistant的addMappedStatement方法
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
MapperBuilderAssistant类的addMappedStatement方法
构建MappedStatement ,添加到configuration的MappedStatements属性中
//很多方法参数
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;
//构建一个builder
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的MappedStatements属性中
configuration.addMappedStatement(statement);
return statement;
}
MappedStatement很关键很关键很关键,后面查询时都是从这里获取xml中的信息 包括sql resultMap等
StatementBuilder的build方法
build方法就很简单了,直接返回,因为在创建builder的时候,属性基本就填充完了。
MappedStatement的各种属性。
3. 将#{} 变成?(静态sql的处理)
变成?后会将里面的参数名称保存
XMLMapperBuilder类的buildStatementFromContext方法
继续本类的buildStatementFromContext
XMLStatementBuilder类的parseStatementNode方法
XMLLanguageDriver类的createSqlSource方法
XMLScriptBuilder类的parseScriptNode方法
RawSqlSource类的RawSqlSource方法
SqlSourceBuilder类的parse方法
GenericTokenParser类的parse方法
关键方法
就是handlertoken方法拼接的问号,并且保存了参数名称
4. 动态sql解析
解析xml时会调用XMLMapperBuilder的buildStatementFromContext方法,遍历所有select delete等标签,调用XMLStatementBuilder类的parseStatementNode方法。
XMLMapperBuilder的parseStatementNode方法
XMLLanguageDriver类的createSqlSource方法
XMLScriptBuilder类的parseScriptNode方法
调用parseDynamicTags方法完成动态sql的解析,如果有解析到动态标签,就把isDynamic置为true。然后构建动态sqlSource。否则构建RawSqlSource
XMLScriptBuilder类的parseDynamicTags方法
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
//拿到当前select标签下所有的子标签
NodeList children = node.getNode().getChildNodes();
//遍历
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
//普通的语句会走这里 ${}应该也是走这里 不常用 这里没做测试
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
//if where foreach等标签会走这里
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//拿到标签的名字
String nodeName = child.getNode().getNodeName();
//根据标签名字 拿到对应的handler
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//调用handler处理,将结果保存到contents中
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
内容都保存在contents中,最后变成MixedSqlNode。
nodeHandlerMap 共有9中 处理动态标签。
XMLScriptBuilder类的内部类的IfHandler类的handleNode方法
继续调用parseDynamicTags解析子标签,也就是if标签里边具体的sql语句,然后获取test标签中的判断内容,然后封装成一个IfSqlNode类型,添加到contents中,然后返回。
最终将所有标签都处理完,返回一个MixedSqlNode用来构建DynamicSqlSource,然后再将DynamicSqlSource添加到mapperedStatement中。后来查询时获取sql时会用到。
最终的sqlsource
后面查询时就是获取这个sqlSource,然后拼接方法传参,得到最终的sql。
5. configuration关键属性
1.mapperRegistry
- bindMapperForNamespace()方法实现的,对接口的全限定名和代理类进行绑定。实例化mapper时会来这里获取到每个mapper对应的MapperProxyFactory,这个工厂类会创建对应的MapperProxy代理类,用来代理mapper的方法,也就相当于对mapper进行了实例化。
2.resultMaps
就是xml中每个方法定义的resultMap或实体类型的resultType
3.sqlFragments
每个xml中定义的sql标签
4.loadedResources
- configuration.addLoadedResource(resource)方法实现的
这里有接口,接口的全限定名,对应的xml。
5.mappedStatements(关键)
这里对应每个< select >、< insert >、< update >、< delete >标签,sqlSource是标签中写的sql,以及对应的resultMap。 - 后期查询时就是通过这个key找到对应的mappedStatement,拿到sqlSource解析成最终的sql,然后进行查询,查询后拿到resultMaps进行参数映射。