概述
mapper.xml是我们使用mybatis接触最多的,需要编写sql、ResultMap、ParameterMap等。且看如何解析xml,并注册到Configuration中。
入口
接着上篇mybatis-confg解析的末尾,关于mapper.xml的解析。
先上实例mapper内容:
入口类是 XMLMapperBuilder,无论是单个还是批量都是循环遍历 XMLMapperBuilder.parse()。
public class XMLMapperBuilder extends BaseBuilder {
// xml解析工具类
private final XPathParser parser;
// Mapper构造小助手(工具类)
private final MapperBuilderAssistant builderAssistant;
// sql片段容器,key=sqlId,value=xmlNode
private final Map<String, XNode> sqlFragments;
// mapper resource()
private final String resource;
}
依旧依靠XPathParser解析。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper />
configurationElement(parser.evalNode("/mapper"));
// 标记该resource已经加载过,防止重复加载
configuration.addLoadedResource(resource);
// 绑定mapper接口 和 xml
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
mapper.xml的根节点是mapper,
来看具体解析过程
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref />
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache />
cacheElement(context.evalNode("cache"));
// 解析<parameterMap /> 已废弃
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap />
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql />
sqlElement(context.evalNodes("/mapper/sql"));
// 解析statement
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
这里依次解析mapper文件中允许出现的标签,
cache-ref
cache引用,共享cache容器的时候使用。
private void cacheRefElement(XNode context) {
if (context != null) {
// 添加缓存引用到Configuration中
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 根据ref查找该cache是否存在
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
cache
真的用的很少,简单扫一眼。
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
parameterMap
mybatis已经在贱贱废弃parameterMap了,我们在日常使用的时候也基本不会使用到,这是老版本提供的功能。略过。
private void parameterMapElement(List<XNode> list) {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
resultMap
结果集映射,日常使用的较多,也比较复杂,解析过程中主要关注日常使用的简单标签。先贴示例的ResultMap内容:
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
这里还涉及到一些不常见的标签例如,association、collection、discriminator,可以在官网看下说明,平时也不太推荐使用,我觉得目前随手可见分库分表的场景,mapper还是保持简单,提供最基础的sql就可以,不要使用什么高级用法,维护、定位问题都麻烦。
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
// 遍历 解析<resultMap />
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 解析type,优先级上到下,四个type的作用是一样的
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 通过alias获取type
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// 遍历子节点,解析成ResultMapping
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);
}
// 构造resultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// resultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
这里解析resultMap的步骤:
-
解析ResultMapping
-
构造器节点解析,构造成ResultMapping。
-
鉴别器解析,构造成ResultMapping。
-
通用解析,构造成ResultMapping。
-
-
构造ResultMap对象
构造器ResultMapping解析-processConstructorElement
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
// 遍历constructor下的子节点
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
// 构造ResultMapping并添加进结果中
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
这里对构造器ResultMapping都设置了ResultFlag.CONSTRUCTOR,对id标签额外添加了ResultFlag.ID。
discriminator(几乎不用,略过)
通用解析
List<ResultFlag> flags = new ArrayList<>();
// id标签解析
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 构造resultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
这里除了constructor、discriminator这两种标签,处理都是相同的。
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.emptyList(), resultType));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
解析结果无非就是值对应,并设置到属性中,比较枯燥。
当有了id、typeClass、ResultMapping之后,ResultMap也就随即产生。
Sql标签解析
在一些重复的sql片段上,我们会使用sql标签进行抽取,以便复用。
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 直接设置了id 和 xcond的映射
sqlFragments.put(id, context);
}
}
}
Statement解析
这里遍历解析mapper下的所有Statement,委托给 XMLStatementBuilder进行解析。
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//解析异常的,记录下来
configuration.addIncompleteStatement(statementParser);
}
}
}
这里Statement的解析也会复杂一些,放到下一篇中单独讲解。
总结
mapper的解析代码还是蛮简单的,根据文档中定义的标签,按顺序解析,解析好的结果会存储到Configuration中。
不过话说 MapperBuilderAssistant 真是一个神奇的类,它是一个工具类,用于在解析Mapper.xml中提供各位创建相关关键类的方法。
例如
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
public ResultMapping buildResultMapping(
Class<?> resultType,
String property,
String column,
Class<?> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class<? extends TypeHandler<?>> typeHandler,
List<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
这种方法入参这么长?不是方法设计的最佳实践,如果在我们开发过程中出现这样的方法,那还不被人砍死。
那mybatis这么做的原因是什么呢?
首先 比如构造ResultMapping确实需要很多属性设置,这里应该是想MapperBuilderAssistant只做纯粹的构造过程。
否则可以将入参改为XNode,在内部进行获取解析,那么入参就短为1个。如果有其他不是通过XNode来创建的场景,那么就重载方法解决喽。
这里考虑到代码的复用的问题,我觉得在应用开发过程中,可以牺牲一点 依赖独立 换更多的代码可读性。
Thats All