From 1edab0bed4398928743cc423e48f9ee4cf8a2abc Mon Sep 17 00:00:00 2001 From: quanhengf Date: Sun, 15 Dec 2019 16:36:41 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=91=E5=AE=9AMapper=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../核心处理层/1、MyBatis初始化.md | 251 +++++++++++++++++- 1 file changed, 247 insertions(+), 4 deletions(-) diff --git a/docs/Mybatis/核心处理层/1、MyBatis初始化.md b/docs/Mybatis/核心处理层/1、MyBatis初始化.md index aaa54aa..84e79ed 100644 --- a/docs/Mybatis/核心处理层/1、MyBatis初始化.md +++ b/docs/Mybatis/核心处理层/1、MyBatis初始化.md @@ -442,14 +442,257 @@ public class ResultMap { private Boolean autoMapping; } ``` -### 3.2 解析<sql>节点 - +了解了ResultMapping 和ResultMap 记录的信息之后,下面开始介绍<resultMap>节点的解析过程。在XMLMapperBuilder中通过resultMapElements()方法解析映射配置文件中的全部<resultMap>节点,该方法会循环调用resultMapElement()方法处理每个<resultMap>节点。 +```java + private ResultMap resultMapElement(XNode resultMapNode) throws Exception { + return resultMapElement(resultMapNode, Collections. emptyList()); + } -## 4 XMLStatementBuilder + private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception { + ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); + // 的id属性,默认值会拼装所有父节点的id 或value或property属性值 + String id = resultMapNode.getStringAttribute("id", + resultMapNode.getValueBasedIdentifier()); + // 的type属性,表示结果集将被映射成type指定类型的对象 + String type = resultMapNode.getStringAttribute("type", + resultMapNode.getStringAttribute("ofType", + resultMapNode.getStringAttribute("resultType", + resultMapNode.getStringAttribute("javaType")))); + // 该属性指定了该节点的继承关系 + String extend = resultMapNode.getStringAttribute("extends"); + // 为true则启动自动映射功能,该功能会自动查找与列明相同的属性名,并调用setter方法, + // 为false,则需要在节点内注明映射关系才会调用对应的setter方法 + Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); + // 解析type类型 + Class typeClass = resolveClass(type); + Discriminator discriminator = null; + // 该集合用来记录解析结果 + List resultMappings = new ArrayList(); + resultMappings.addAll(additionalResultMappings); + // 获取并处理的子节点 + List resultChildren = resultMapNode.getChildren(); + // child单数形式,children复数形式 + 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 flags = new ArrayList(); + if ("id".equals(resultChild.getName())) { + flags.add(ResultFlag.ID); + } + // 创建ResultMapping对象,并添加到resultMappings集合 + resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); + } + } + ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); + try { + return resultMapResolver.resolve(); + } catch (IncompleteElementException e) { + configuration.addIncompleteResultMap(resultMapResolver); + throw e; + } + } +``` +从上面的代码我们可以看到,mybatis从<resultMap>节点获取到id属性和type属性值之后,就会通过XMLMapperBuilder的buildResultMappingFromContext()方法为<result>节点创建对应的ResultMapping 对象。 +```java + /** + * 根据上下文环境构建ResultMapping + */ + private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List 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())); + 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); + @SuppressWarnings("unchecked") + Class> typeHandlerClass = (Class>) resolveClass(typeHandler); + JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); + // 创建ResultMapping对象并返回 + return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); + } +``` +得到ResultMapping对象集合之后,会调用ResultMapResolver的resolve()方法,该方法会调用MapperBuilderAssistant的addResultMap()方法创建ResultMap对象,并将ResultMap对象添加到Configuration的resultMaps集合中保存。 +```java +public class MapperBuilderAssistant extends BaseBuilder { + public ResultMap addResultMap(String id, Class type, String extend, + Discriminator discriminator, List resultMappings, Boolean autoMapping) { + // ResultMap的完整id是"namespace.id"的格式 + id = applyCurrentNamespace(id, false); + // 获取 父ResultMap的完整id + extend = applyCurrentNamespace(extend, true); -## 5 绑定Mapper接口 + // 针对extend属性进行的处理 + 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对象的ResultMapping集合 + List extendedResultMappings = new ArrayList(resultMap.getResultMappings()); + // 删除需要覆盖的ResultMapping集合 + extendedResultMappings.removeAll(resultMappings); + // Remove parent constructor if this resultMap declares a constructor. + boolean declaresConstructor = false; + for (ResultMapping resultMapping : resultMappings) { + if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { + declaresConstructor = true; + break; + } + } + if (declaresConstructor) { + Iterator extendedResultMappingsIter = extendedResultMappings.iterator(); + while (extendedResultMappingsIter.hasNext()) { + if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) { + extendedResultMappingsIter.remove(); + } + } + } + // 添加需要被继承下来的ResultMapping集合 + resultMappings.addAll(extendedResultMappings); + } + ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) + .discriminator(discriminator) + .build(); + configuration.addResultMap(resultMap); + return resultMap; + } +} +``` +### 3.2 解析<sql>节点 +在映射配置文件中,可以使用<sql>节点定义可重用的SQL语句片段,当需要重用<sql>节点中定义的SQL语句片段时,只需要使用<include>节点引入相应的片段即可,这样,在编写SQL语句以及维护这些SQL语句时,都会比较方便。XMLMapperBuilder的sqlElement()方法负责解析映射配置文件中定义的全部<sql>节点。 +```java + private void sqlElement(List list) throws Exception { + if (configuration.getDatabaseId() != null) { + sqlElement(list, configuration.getDatabaseId()); + } + sqlElement(list, null); + } + private void sqlElement(List list, String requiredDatabaseId) throws Exception { + // 遍历节点 + for (XNode context : list) { + String databaseId = context.getStringAttribute("databaseId"); + String id = context.getStringAttribute("id"); + // 为id添加命名空间 + id = builderAssistant.applyCurrentNamespace(id, false); + // 检测的databaseId与当前Configuration中记录的databaseId是否一致 + if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { + // 记录到sqlFragments(Map)中保存 + sqlFragments.put(id, context); + } + } + } +``` +## 4 XMLStatementBuilder +这一部分看的不是很懂,暂时保留,日后深入理解了再写。 +## 5 绑定Mapper接口 +通过之前对binding模块的解析可知,每个映射配置文件的命名空间可以绑定一个Mapper接口,并注册到MapperRegistry中。XMLMapperBuilder的bindMapperForNamespace()方法中,完成了映射配置文件与对应Mapper 接 +口的绑定。 +```java +public class XMLMapperBuilder extends BaseBuilder { + private void bindMapperForNamespace() { + // 获取映射配置文件的命名空间 + String namespace = builderAssistant.getCurrentNamespace(); + if (namespace != null) { + Class boundType = null; + try { + // 解析命名空间对应的类型 + boundType = Resources.classForName(namespace); + } catch (ClassNotFoundException e) { + //ignore, bound type is not required + } + if (boundType != null) { + // 是否已加载boundType接口 + if (!configuration.hasMapper(boundType)) { + // 追加个"namespace:"的前缀,并添加到Configuration的loadedResources集合中 + configuration.addLoadedResource("namespace:" + namespace); + // 添加到Configuration的mapperRegistry集合中,另外,往这个方法栈的更深处看 会发现 + // 其创建了MapperAnnotationBuilder对象,并调用了该对象的parse()方法解析Mapper接口 + configuration.addMapper(boundType); + } + } + } + } +} +public class MapperRegistry { + public void addMapper(Class type) { + if (type.isInterface()) { + if (hasMapper(type)) { + throw new BindingException("Type " + type + " is already known to the MapperRegistry."); + } + boolean loadCompleted = false; + try { + knownMappers.put(type, new MapperProxyFactory(type)); + // 解析Mapper接口type中的信息 + MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); + parser.parse(); + loadCompleted = true; + } finally { + if (!loadCompleted) { + knownMappers.remove(type); + } + } + } + } +} +public class MapperAnnotationBuilder { + public void parse() { + String resource = type.toString(); + // 是否已经加载过该接口 + if (!configuration.isResourceLoaded(resource)) { + // 检查是否加载过该接口对应的映射文件,如果未加载,则创建XMLMapperBuilder对象 + // 解析对应的映射文件,该过程就是前面介绍的映射配置文件解析过程 + loadXmlResource(); + configuration.addLoadedResource(resource); + assistant.setCurrentNamespace(type.getName()); + // 解析@CacheNamespace注解 + parseCache(); + // 解析@CacheNamespaceRef注解 + parseCacheRef(); + // type接口的所有方法 + Method[] methods = type.getMethods(); + for (Method method : methods) { + try { + if (!method.isBridge()) { + // 解析SelectKey、ResultMap等注解,并创建MappedStatement对象 + parseStatement(method); + } + } catch (IncompleteElementException e) { + // 如果解析过程出现IncompleteElementException异常,可能是因为引用了 + // 未解析的注解,这里将出现异常的方法记录下来,后面提供补偿机制,重新进行解析 + configuration.addIncompleteMethod(new MethodResolver(this, method)); + } + } + } + // 遍历configuration中的incompleteMethods集合,集合中记录了未解析的方法 + // 重新调用这些方法进行解析 + parsePendingMethods(); + } +} +``` +另外,在MapperAnnotationBuilder的parse()方法中解析的注解,都能在映射配置文件中找到与之对应的XML节点,且两者的解析过程也非常相似。 \ No newline at end of file