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引用,共享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); } } }真的用的很少,简单扫一眼。
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); } }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 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都设置了ResultFlag.CONSTRUCTOR,对id标签额外添加了ResultFlag.ID。
这里除了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标签进行抽取,以便复用。
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); } } }这里遍历解析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