//代码比较长了,因为parameterMap 涉及到比较多的东西 // <resultMap id="userMap" type="com.test.demo.model.SysUser"> // <id property="id" column="id"/> // <result property="userPassword" column="user_password"/> // <result property="userName" column="user_name"/> // <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/> // </resultMap> private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception { //首先记录一下跟踪日志,有关ErrorContext后面会详细明说 ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); //这里可以看到用了一连串的嵌套,其实就是设置默认值而已 //简单解释这里的操作就是:首先获取`type`的值 //如果`type`为空就取`ofType`的值 //如果`ofType`为空就取`resultType`的值 //如果`resultType`为空就取`javaType`的值 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); //解析这个类 Class<?> typeClass = resolveClass(type); //如果type为null //一般resolveClass只有在type为null的时候才会返回null if (typeClass == null) { //这里暂时没看懂,因为解析`ResultMap`传入的enclosingType为null typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<>(); resultMappings.addAll(additionalResultMappings); //解析子节点 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>,则将其保存起来,后续用来充当equals的作用 if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } //处理其他的子节点 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } //获取此节点的id String id = resultMapNode.getStringAttribute("id", //不存在则自动生成一个唯一id resultMapNode.getValueBasedIdentifier()); //获取继承节点信息 String extend = resultMapNode.getStringAttribute("extends"); //是否自动映射 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); //创建解析类 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { //进行解析 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
说实话,这段代码是在比较难理解,就光前面的嵌套获取 type
/ ofType
/ jdbcType
等找了十万遍都没找到为什么。。甚至想到了是不是兼容老版本的问题。。可以看提交记录这些代码9年前都有了。。。
太难了
后来配合官方文档看,也能大概明白:
<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>
可以发现 resultMap
中可以嵌套 <constructor>
或者 <collection>
而 <collection>
中又可以嵌套其他的,比如 <association>
/ <collection>
那如果要完整的解析这些嵌套的东西,最好的办法就是递归,
这就是为什么前面会有3中 type
的获取,因为这个方法不仅仅是在解析 <resultMap>
,还会被递归调用来解析 <association>
/ <collection>
等等。
先跳过其他的代码,我们先看看解析其他子节点
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", //处理嵌套的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); }
这里最重要的便是处理嵌套的resultMap
一般来说,能被嵌套的元素有:
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) throws Exception { if ("association".equals(context.getName()) || "collection".equals(context.getName()) || "case".equals(context.getName())) { //先不解析动态SQL if (context.getStringAttribute("select") == null) { //验证collection节点是否包含必须要元素 //必须包含resultMap 和 javaType其中一个 validateCollection(context, enclosingType); //调用最开始的方法,进行递归解析 ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType); return resultMap.getId(); } } return null; }
这里就能能看出,已经开始递归调用了
接下来,我们以 <association> / <collection>
为主体元素,再次分析一遍 resultMapElement
的源代码
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); //对于resultMap 获取 type //对于collection 获取 ofType 或 javaType //对于association 获取 javaType String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); //加载此type,可以指定别名 Class<?> typeClass = resolveClass(type); if (typeClass == null) { //这里enclosingType一般都是父节点的type //比如<resultMap type="test"> // <collection/> //那么enclosingType便是test //如果有些节点没有配置type,允许的情况下,可以直接使用父节点的type //例如: // <discriminator javaType="int" column="draft"> // <case value="1" resultType="DraftPost"/> // </discriminator> // case节点中就没有配置type typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<>(); //添加已经解析过的resultMap resultMappings.addAll(additionalResultMappings); //继续解析子节点 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<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } //获取id 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; } }
接下来看其他细节:
当子节点解析完了以后,会将子节点添加到 resultMapping
中,然后再解析最大的 resultMap
//resultMapResolver.resolve();内部调用的此方法 public ResultMap addResultMap( String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { //首先给当前标签加上命名空间前缀 id = applyCurrentNamespace(id, false); //然后给继承的标签加上命名空间前缀 //从这个当前前缀可以看出来,继承只能继承当前命名空间的元素 extend = applyCurrentNamespace(extend, true); //判断是否有集成的属性 if (extend != null) { //如果所继承的属性还没有解析,那么抛出指定异常,稍后再解析 if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'"); } //通过继承找到对应的resultMap ResultMap resultMap = configuration.getResultMap(extend); //获取这个resultMap id 下对应的所有resultMap (包括嵌套resultMap) List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); //去除重复resultMap extendedResultMappings.removeAll(resultMappings); // Remove parent constructor if this resultMap declares a constructor. boolean declaresConstructor = false; //检查是否需要使用带参构造方法构造resultMap for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true; break; } } //如果需要使用构造方法创键`resultMap type`,那么将此需要使用构造方法的元素从继承元素中删除 if (declaresConstructor) { extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)); } resultMappings.addAll(extendedResultMappings); } //构造resultMap ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); configuration.addResultMap(resultMap); return resultMap; }
这几段代码看着实在累,第一是比较复杂,第二个就是本来想要实现的功能也比较复杂。
这里可以看见 MyBatis
仅仅是将嵌套的 resultMap
分解为几个 reusltMap
,然后放入 list
中,并且这里解析配置就真正的只是解析配置,没有做任何多余的事、
同时可以看出来, MyBatis
的模块划分是非常好的,
XMLConfigBuilder
-> XMLMapperBuilder
-> MapperBuilderAssistant
XMLMapperBuilder
只用负责读取配置文件,而将配置文件生成对像则交给 MapperBuilderAssistant
看上面的代码, resultMap
也只解析了静态部分能够解析的地方,而需要动态生成的则直接原封不动的放入了 Configuration
类中