栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Mybatis(四)映射文件解析流程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Mybatis(四)映射文件解析流程

文章目录
      • 1、映射文件解析解析入口
      • 2、解析映射文件
        • 2.1 解析< cache >节点
        • 2.2 解析< cache-ref >节点
        • 2.3 解析< resultMap >节点
        • 2.4 解析< sql >节点
        • 2.5 解析 SQL 语句节点
      • 3、Mapper接口绑定过程
      • 4、处理未完成解析的节点

本系列文章:
  Mybatis(一)Mybatis的基本使用
  Mybatis(二)Mybatis的高级使用
  Mybatis(三)配置文件解析流程
  Mybatis(四)映射文件解析流程
  Mybatis(五)SQL执行流程
  Mybatis(六)数据源、缓存机制、插件机制

  与配置文件不同,映射文件用于配置SQL语句,字段映射关系等。映射文件中包含等二级节点,这些节点将在接下来内容中进行分析。除了分析常规的 XML 解析过程外,还会介绍Mapper接口的绑定过程,以及其他一些知识。

1、映射文件解析解析入口

  映射文件的解析过程是配置文件解析过程的一部分,MyBatis会在解析配置文件的过程中对映射文件进行解析。解析逻辑封装在XMLConfigBuilder中的mapperElement方法中:

  	private void mapperElement(XNode parent) throws Exception {
    	if (parent != null) {
      		for (XNode child : parent.getChildren()) {
        		if ("package".equals(child.getName())) {
          			//获取节点中的name属性
          			String mapperPackage = child.getStringAttribute("name");
          			//从指定包中查找mapper接口,并根据mapper接口解析映射配置
          			configuration.addMappers(mapperPackage);
        		} else {
          			// 获取resource、url、class等属性
          			String resource = child.getStringAttribute("resource");
          			String url = child.getStringAttribute("url");
          			String mapperClass = child.getStringAttribute("class");
          			//resource不为空,且其他两者为空,则从指定路径中加载配置
          			if (resource != null && url == null && mapperClass == null) {
            			ErrorContext.instance().resource(resource);
            			InputStream inputStream = Resources.getResourceAsStream(resource);
            			XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            			//解析映射文件
            			mapperParser.parse();
          				//url 不为空,且其他两者为空,则通过url加载配置
          			} else
          			 if (resource == null && url != null && mapperClass == null) {
            			ErrorContext.instance().resource(url);
            			InputStream inputStream = Resources.getUrlAsStream(url);
            			XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            			//解析映射文件
            			mapperParser.parse();
          				//mapperClass不为空,且其他两者为空,则通过mapperClass解析映射配置
          			} else if (resource == null && url == null && mapperClass != null) {
            			Class mapperInterface = Resources.classForName(mapperClass);
            			configuration.addMapper(mapperInterface);
          			// 以上条件不满足,则抛出异常
          			} else {
            			throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          			}
        		}
      		}
    	}
  	}

  代码的主要逻辑是遍历mappers的子节点,并根据节点属性值判断通过何种方式加载映射文件或映射信息。这里把配置在注解中的内容称为映射信息,以XML为载体的配置称为映射文件。
  在MyBatis中,共有四种加载映射文件或映射信息的方式:

  1. 从文件系统中加载映射文件;
  2. 通过URL的方式加载映射文件;
  3. 通过mapper接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中;
  4. 通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。

  在 MyBatis中,通过注解配置映射信息的方式是有一定局限性的,这一点MyBatis官方文档中描述的比较清楚:

  最初设计时,MyBatis是一个XML驱动的框架。配置信息是基于XML的,而且映射语句也是定义在XML中的。而到了MyBatis3,就有新选择了。MyBatis3构建在全面且强大的基于Java语言的配置API之上。这个配置API是基于XML的MyBatis配置的基础,也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销。
  注意:Java注解的表达力和灵活性十分有限。尽管很多时间都花在调查、设计和试验上,最强大的MyBatis映射并不能用注解来构建。

  可以看出:限于Java注解的表达力和灵活性,通过注解的方式并不能完全发挥MyBatis的能力。因此,对于一些较为复杂的配置信息,还是应该通过XML 的方式进行配置。
  下面开始分析映射文件的解析过程,在分析之前,先来看一下映射文件解析入口。在上面的mapperElement方法中调用了mapperParser.parse()方法,这就是我们想要的入口,即XMLMapperBuilder中的parse方法:

  	public void parse() {
    	//检测映射文件是否已经被解析过
    	if (!configuration.isResourceLoaded(resource)) {
      		//解析mapper节点
      		configurationElement(parser.evalNode("/mapper"));
      		//添加资源路径到“已解析资源集合”中
      		configuration.addLoadedResource(resource);
      		//通过命名空间绑定Mapper接口
      		bindMapperForNamespace();
    	}
    	//处理未完成解析的节点
    	parsePendingResultMaps();
    	parsePendingCacheRefs();
    	parsePendingStatements();
  	}

  映射文件解析入口逻辑包含三个核心操作:

  1. 解析 mapper 节点。
  2. 通过命名空间绑定 Mapper 接口。
  3. 处理未完成解析的节点。
2、解析映射文件

  映射文件包含多种二级节点 , 比如 以 及 等。除此之外,还包含了一些三级节点,比如 等。先来看一个映射文件配置示例:

	
		
		
			
			
			
		
		
		   author
		
		、...、等节点
      		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);
    	}
  	}

  在阅读源码时,按部就班地分析每个方法调用即可。不过接下来在叙述的过程中会对分析顺序进行一些调整,本章将会先分析节点的解析过程,然后再分析节点,之后会按照顺序分析其他节点的解析过程。

2.1 解析< cache >节点

  MyBatis 提供了一、二级缓存,其中一级缓存是 SqlSession 级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显示配置才能开启。如果无特殊要求,二级缓存的配置很简单。

	

  如果想修改缓存的一些属性,可以像下面这样配置:

	

  上面配置的意思是:

  1. 按先进先出的策略淘汰缓存项。
  2. 缓存的容量为512个对象引用。
  3. 缓存每隔60秒刷新一次。
  4. 缓存返回的对象是写安全的,即在外部修改对象不会影响到缓存内部存储对象。

  当然,除了使用Mybatis自带的缓存,也可以使用第三方缓存,比如Ehcache:

	
		
		
		
		
		
	

  缓存配置的解析逻辑在XMLMapperBuilder中实现:

  	private void cacheElement(XNode context) {
    	if (context != null) {
      		//获取各种属性
      		String type = context.getStringAttribute("type", "PERPETUAL");
      		Class typeClass = typeAliasRegistry.resolveAlias(type);
      		String eviction = context.getStringAttribute("eviction", "LRU");
      		Class 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);
    	}
  	}

  上面代码中,大段代码用来解析节点的属性和子节点,缓存对象的构建逻辑封装在 MapperBuilderAssistant类的useNewCache方法中:

  	public Cache useNewCache(Class typeClass,
      	Class evictionClass,
      	Long flushInterval,
      	Integer size,
      	boolean readWrite,
      	boolean blocking,
      	Properties props) {
    	//使用建造模式构建缓存实例
    	Cache cache = new CacheBuilder(currentNamespace)
        	.implementation(valueOrDefault(typeClass, PerpetualCache.class))
        	.addDecorator(valueOrDefault(evictionClass, LruCache.class))
        	.clearInterval(flushInterval)
        	.size(size)
        	.readWrite(readWrite)
        	.blocking(blocking)
        	.properties(props)
       	 	.build();
    	//添加缓存到Configuration对象中
    	configuration.addCache(cache);
    	//设置currentCache遍历,即当前使用的缓存
   	 	currentCache = cache;
    	return cache;
  	}

  接下来看下Cache 实例构建过程,在CacheBuilder中实现:

  	public Cache build() {
    	//设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
    	setDefaultImplementations();
    	//通过反射创建缓存
    	Cache cache = newBaseCacheInstance(implementation, id);
    	setCacheProperties(cache);
    	//仅对内置缓存PerpetualCache应用装饰器
    	if (PerpetualCache.class.equals(cache.getClass())) {
      		//遍历装饰器集合,应用装饰器
      		for (Class decorator : decorators) {
        		//通过反射创建装饰器实例
        		cache = newCacheDecoratorInstance(decorator, cache);
        		//设置属性值到缓存实例中
        		setCacheProperties(cache);
      		}
      		//应用标准的装饰器,比如 LoggingCache、SynchronizedCache
      		cache = setStandardDecorators(cache);
    	} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      		//应用具有日志功能的缓存装饰器
      		cache = new LoggingCache(cache);
    	}
    	return cache;
  	}

  上面的代码可以分为4步:

  1. 设置默认的缓存类型及装饰器。
  2. 应用装饰器到PerpetualCache对象上。
  3. 应用标准装饰器。
  4. 对非LoggingCache类型的缓存应用LoggingCache装饰器。

  最后一步的逻辑很简单,面按顺序分析前 3 个步骤:

  	private void setDefaultImplementations() {
    	if (implementation == null) {
     	 	//设置默认的缓存实现类
      		implementation = PerpetualCache.class;
      		if (decorators.isEmpty()) {
        		//添加LruCache装饰器
        		decorators.add(LruCache.class);
      		}
    	}
  	}

  以上代码主要做的事情是在implementation为空的情况下,为它设置一个默认值。其实在调用setDefaultImplementations方法之前已经进行了很多非空判断,implementation不可能为空,setDefaultImplementations 方法似乎没有存在的必要了。其实不然,如果有人不按套路写代码。比如:

	Cache cache = new CacheBuilder(currentNamespace)
		//忘记设置implementation
		.build();

  忘记设置implementation ,或人为的将implementation设为空。如果不对implementation进行判空,会导致build方法在构建实例时触发空指针异常,对于框架来说,这是一个低级错误。这种情况一定要避免,以提高框架的健壮性。
  接下来看下,如果使用第三方缓存时,其对应的配置是如何设置到缓存实例中的。

  	private void setCacheProperties(Cache cache) {
    	if (properties != null) {
      		//为缓存实例生成一个“元信息”实例,forObject方法调用层次比较深,
      		//但最终调用了MetaClass的forClass方法。
      		MetaObject metaCache = SystemMetaObject.forObject(cache);
      		for (Map.Entry entry : properties.entrySet()) {
        		String name = (String) entry.getKey();
        		String value = (String) entry.getValue();
        		if (metaCache.hasSetter(name)) {
          			//获取setter方法的参数类型
          			Class type = metaCache.getSetterType(name);
          			//根据参数类型对属性值进行转换,并将转换后的值
          			//通过setter方法设置到Cache实例中
          			if (String.class == type) {
            			metaCache.setValue(name, value);
          			} else if (int.class == type
              			|| Integer.class == type) {
            			
            			metaCache.setValue(name, Integer.valueOf(value));
         			 } else if (long.class == type
              			|| Long.class == type) {
            			metaCache.setValue(name, Long.valueOf(value));
          			} else if (short.class == type
              			|| Short.class == type) {
            			metaCache.setValue(name, Short.valueOf(value));
          			} else if (byte.class == type
              			|| Byte.class == type) {
            			metaCache.setValue(name, Byte.valueOf(value));
          			} else if (float.class == type
              			|| Float.class == type) {
            			metaCache.setValue(name, Float.valueOf(value));
          			} else if (boolean.class == type
              			|| Boolean.class == type) {
            			metaCache.setValue(name, Boolean.valueOf(value));
          			} else if (double.class == type
              			|| Double.class == type) {
            			metaCache.setValue(name, Double.valueOf(value));
          			} else {
            			throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          			}
        		}
      		}
    	}
    	//如果缓存类实现了InitializingObject接口,
    	//则调用initialize方法执行初始化逻辑
    	if (InitializingObject.class.isAssignableFrom(cache.getClass())) {
      		try {
        		((InitializingObject) cache).initialize();
      		} catch (Exception e) {
        		throw new CacheException("Failed cache initialization for '"
          + cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      		}
    	}
  	}

  上面的大段代码用于对属性值进行类型转换,和设置转换后的值到Cache实例中。
  最后,看一下设置标准装饰器的过程。

  	private Cache setStandardDecorators(Cache cache) {
    	try {
      		//创建“元信息”对象
      		MetaObject metaCache = SystemMetaObject.forObject(cache);
      		if (size != null && metaCache.hasSetter("size")) {
        		//设置size属性
        		metaCache.setValue("size", size);
      		}
      		if (clearInterval != null) {
        		//clearInterval不为空,应用ScheduledCache装饰器
        		cache = new ScheduledCache(cache);
        		((ScheduledCache) cache).setClearInterval(clearInterval);
      		}
      		if (readWrite) {
        		//readWrite为true,应用SerializedCache装饰器
        		cache = new SerializedCache(cache);
      		}
      		
      		cache = new LoggingCache(cache);
      		cache = new SynchronizedCache(cache);
      		if (blocking) {
        		//blocking为true,应用BlockingCache装饰器
        		cache = new BlockingCache(cache);
      		}
      		return cache;
    	} catch (Exception e) {
      		throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    	}
  	}

  以上代码用于为缓存应用一些基本的装饰器,除了LoggingCache和SynchronizedCache这两个是必要的装饰器,其他的装饰器应用与否,取决于用户的配置。

2.2 解析< cache-ref >节点

  在MyBatis中,二级缓存是可以共用的。这需要通过节点为命名空间配置参照缓存,示例:

	
	
		
		
	
	
	
		
	

  cache-ref的解析过程还是在XMLMapperBuilder中实现的:

  	private void cacheRefElement(XNode context) {
    	if (context != null) {
      		configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      		//创建CacheRefResolver实例
      		CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      		try {
        		//解析参照缓存
        		cacheRefResolver.resolveCacheRef();
      		} catch (IncompleteElementException e) {
        		//捕捉IncompleteElementException异常,并将cacheRefResolver
        		//存入到Configuration的incompleteCacheRefs集合中
        		configuration.addIncompleteCacheRef(cacheRefResolver);
      		}
    	}
  	}

  节点的解析逻辑封装在了CacheRefResolver的resolveCacheRef方法中:

  	public Cache resolveCacheRef() {
    	//调用builderAssistant的useNewCache(namespace)方法
    	return assistant.useCacheRef(cacheRefNamespace);
  	}

  上述代码又调用了MapperBuilderAssistant中的useCacheRef方法:

  	public Cache useCacheRef(String namespace) {
    	if (namespace == null) {
      		throw new BuilderException("cache-ref element requires a namespace attribute.");
    	}
    	try {
      		unresolvedCacheRef = true;
      		//根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例
      		Cache cache = configuration.getCache(namespace);
      		
      		if (cache == null) {
        		throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      		}
      		//设置cache为当前使用缓存
      		currentCache = cache;
      		unresolvedCacheRef = false;
      		return cache;
    	} catch (IllegalArgumentException e) {
      		throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    	}
  	}
2.3 解析< resultMap >节点

  resultMap是MyBatis框架中常用的特性,主要用于映射结果。
  resultMap配置的解析过程也是在XMLMapperBuilder中实现的:

  	private void resultMapElements(List list) {
    	//遍历节点列表
    	for (XNode resultMapNode : list) {
      		try {
        		//解析resultMap节点
        		resultMapElement(resultMapNode);
      		} catch (IncompleteElementException e) {
      		}
    	}
  	}

  	private ResultMap resultMapElement(XNode resultMapNode) {
    	//调用重载方法
    	return resultMapElement(resultMapNode, Collections.emptyList(), null);
  	}

  	private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings, Class enclosingType) {
    	ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    	//获取type属性
    	String type = resultMapNode.getStringAttribute("type",
        	resultMapNode.getStringAttribute("ofType",
            	resultMapNode.getStringAttribute("resultType",
                	resultMapNode.getStringAttribute("javaType"))));
    	//解析type属性对应的类型
    	Class typeClass = resolveClass(type);
    	if (typeClass == null) {
      		typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    	}
    	Discriminator discriminator = null;
    	List resultMappings = new ArrayList<>(additionalResultMappings);
    	//获取并遍历的子节点列表
    	List resultChildren = resultMapNode.getChildren();
    	for (XNode resultChild : resultChildren) {
      		if ("constructor".equals(resultChild.getName())) {
        		//解析constructor节点,并生成相应的ResultMapping
        		processConstructorElement(resultChild, typeClass, resultMappings);
      		} else if ("discriminator".equals(resultChild.getName())) {
        		//解析discriminator节点
        		discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      		} else {
        		List flags = new ArrayList<>();
        		if ("id".equals(resultChild.getName())) {
          			//添加ID到flags集合中
          			flags.add(ResultFlag.ID);
        		}
        		//解析id和property节点,并生成相应的ResultMapping
        		resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      		}
    	}
    	//获取id属性
    	String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    	//获取extends和autoMapping
    	String extend = resultMapNode.getStringAttribute("extends");
    	Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    	ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    	try {
      		//根据前面获取到的信息构建ResultMap对象
      		return resultMapResolver.resolve();
    	} catch (IncompleteElementException e) {
     	 	
      		configuration.addIncompleteResultMap(resultMapResolver);
      		throw e;
    	}
  	}

  上面的代码做的事情:

  1. 获取节点的各种属性。
  2. 遍历的子节点,并根据子节点名称执行相应的解析逻辑。
  3. 构建 ResultMap 对象。
  4. 若构建过程中发生异常,则将resultMapResolver添加到incompleteResultMaps 集合中。

  第2步和第3步分别是节点的子节点解析过程,以及ResultMap对象的构建过程,这两个过程比较重要。

  • 1、 解析< id >和< result >节点
      在节点中,子节点都是常规配置,这两个节点的解析是在XMLMapperBuilder中进行的:
  	private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) {
    	String property;
    	//根据节点类型获取name或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");
    	//解析resultMap属性,该属性出现在和节点中。
    	//若这两个节点不包含resultMap属性,则调用processNestedResultMappings方法
    	//解析嵌套resultMap。
    	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"));
    	//解析javaType、typeHandler 的类型以及枚举类型JdbcType
    	Class javaTypeClass = resolveClass(javaType);
    	Class> typeHandlerClass = resolveClass(typeHandler);
    	JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    	//构建ResultMapping对象
    	return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  	}

  上面的代码主要用于获取节点的属性。resultMap属性的解析过程要相对复杂一些。该属性存在于和节点中。下面以节点为例,演示该节点的两种配置方式。
  第一种配置方式是通过resultMap属性引用其他的节点:

	
		
		
		
	
	
		
		
	

  第二种配置方式是采取resultMap嵌套的方式进行配置:

	
		
		
		
			
			
		
	

  的子节点是一些结果映射配置,这些结果配置最终也会被解析成ResultMap。解析过程仍在XMLMapperBuilder中:

  	private String processNestedResultMappings(XNode context, List resultMappings, Class enclosingType) {
    	//判断节点名称
    	if (Arrays.asList("association", "collection", "case").contains(context.getName())
        	&& context.getStringAttribute("select") == null) {
      		validateCollection(context, enclosingType);
      		//resultMapElement是解析ResultMap入口方法
      		ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
      		//返回resultMap id
      		return resultMap.getId();
    	}
    	return null;
  	}

  的子节点由resultMapElement方法解析成ResultMap,并在最后返回resultMap.id。对于节点,id的值配置在该节点的id属性中。但节点无法配置id属性,那么该id如何产生的呢?答案在XNode类的getValueBasedIdentifier方法中。
  接下来分析ResultMapping的构建过程,该过程是在MapperBuilderAssistant中实现的:

  	public ResultMapping buildResultMapping(Class resultType,
      	String property,String column,Class javaType,
      	JdbcType jdbcType,String nestedSelect,String nestedResultMap,
      	String notNullColumn,String columnPrefix,
      	Class> typeHandler,List flags,
      	String resultSet,String foreignColumn,boolean lazy) {
    	//若javaType为空,这里根据property的属性进行解析。方法中的参数说明:
    	// - resultType:即中的type属性
    	// - property:即中的property属性
    	Class javaTypeClass = resolveResultJavaType(resultType, property, javaType);
    	//解析TypeHandler
    	TypeHandler typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
    	//解析column = {property1=column1, property2=column2}的情况,
    	//这里会将column拆分成多个ResultMapping
    	List composites;
    	if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
      		composites = Collections.emptyList();
    	} else {
      		composites = parseCompositeColumnName(column);
    	}
    	//通过建造模式构建ResultMapping
    	return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
        	.jdbcType(jdbcType)
        	.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
        	.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
        	.resultSet(resultSet)
        	.typeHandler(typeHandlerInstance)
        	.flags(flags == null ? new ArrayList<>() : flags)
        	.composites(composites)
        	.notNullColumns(parseMultipleColumnNames(notNullColumn))
       	 	.columnPrefix(columnPrefix)
        	.foreignColumn(foreignColumn)
        	.lazy(lazy)
        	.build();
  	}

  接着调用了ResultMapping中的build方法:

    public ResultMapping build() {
      	//将flags和composites两个集合变为不可修改集合
      	resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
      	resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
      	//从TypeHandlerRegistry中获取相应TypeHandler
      	resolveTypeHandler();
      	validate();
      	return resultMapping;
    }

  ResultMapping的构建过程不是很复杂,首先是解析javaType类型,并创建typeHandler实例。然后处理复合column。最后通过建造器构建ResultMapping实例。

  • 2、解析< constructor >节点
      有时开发时会用到特殊些的POJO,比如把POJO的setter方法移除,增加构造方法用于初始化成员变量。对于这种不可变的Java类,需要通过带有参数的构造方法进行初始化(反射也可以达到同样目的)。此时会用到节点,示例:
	
		
		
		
	

  该节点的解析是在XMLMapperBuilder中的:

  	private void processConstructorElement(XNode resultChild, Class resultType, List resultMappings) {
    	//获取子节点列表
    	List argChildren = resultChild.getChildren();
    	for (XNode argChild : argChildren) {
      		List flags = new ArrayList<>();
      		//向flags中添加CONSTRUCTOR标志
      		flags.add(ResultFlag.CONSTRUCTOR);
      		if ("idArg".equals(argChild.getName())) {
        		//向flags中添加ID标志
        		flags.add(ResultFlag.ID);
      		}
      		//构建ResultMapping
      		resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    	}
  	}

  首先是获取并遍历子节点列表,然后为每个子节点创建flags集合,并添加CONSTRUCTOR标志。对于idArg节点,额外添加ID标志。最后一步则是构建ResultMapping。

  • 3、ResultMap 对象构建过程分析
      通过前面的分析,可知等节点最终都被解析成了ResultMapping。在得到这些ResultMapping后,紧接着要做的事情是构建ResultMap。
      ResultMap构建的入口:
	private ResultMap resultMapElement(XNode resultMapNode,
  		List additionalResultMappings) throws Exception {
		//获取resultMap节点中的属性
		// ...
		//解析resultMap对应的类型
		// ...
		//遍历resultMap节点的子节点,构建ResultMapping对象
		// ...
		//创建ResultMap解析器
		ResultMapResolver resultMapResolver = new ResultMapResolver(
			builderAssistant, id, typeClass, extend, discriminator,
			resultMappings, autoMapping);
		try {
			//根据前面获取到的信息构建ResultMap对象
			return resultMapResolver.resolve();
	 	} catch (IncompleteElementException e) {
			configuration.addIncompleteResultMap(resultMapResolver);
			throw e;
	 	}
	}

  ResultMap的构建逻辑封装在ResultMapResolver的resolve方法中:

  	public ResultMap resolve() {
    	return assistant.addResultMap(this.id, this.type, this.extend, 	
    		this.discriminator, this.resultMappings, this.autoMapping);
  	}

  上面的方法将构建ResultMap实例的任务委托给了MapperBuilderAssistant的addResultMap:

  	public ResultMap addResultMap(
      	String id,Class type,String extend,Discriminator discriminator,
      	List resultMappings,Boolean autoMapping) {
    	//为ResultMap的id和extend属性值拼接命名空间
    	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 = configuration.getResultMap(extend);
      		List extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      		//为拓展ResultMappings取出重复项
      		extendedResultMappings.removeAll(resultMappings);
      		boolean declaresConstructor = false;
      		//检测当前resultMappings集合中是否包含CONSTRUCTOR标志的元素
      		for (ResultMapping resultMapping : resultMappings) {
        		if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          			declaresConstructor = true;
          			break;
        		}
      		}
      		//如果当前节点中包含子节点,
      		//则将拓展ResultMapping 集合中的包含CONSTRUCTOR标志的元素移除
      		if (declaresConstructor) {
        		extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
      		}
      		//将扩展resultMappings集合合并到当前resultMappings集合中
      		resultMappings.addAll(extendedResultMappings);
    	}
    	//构建ResultMap
    	ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        	.discriminator(discriminator)
        	.build();
    	configuration.addResultMap(resultMap);
    	return resultMap;
  	}

  上面的方法主要用于处理resultMap节点的extend属性,extend不为空的话,这里将当前resultMappings集合和扩展resultMappings集合合二为一。随后,通过建造模式,在ResultMap中构建ResultMap实例。

    public ResultMap build() {
      	if (resultMap.id == null) {
        	throw new IllegalArgumentException("ResultMaps must have an id");
      	}	
      	resultMap.mappedColumns = new HashSet<>();
      	resultMap.mappedProperties = new HashSet<>();
      	resultMap.idResultMappings = new ArrayList<>();
      	resultMap.constructorResultMappings = new ArrayList<>();
      	resultMap.propertyResultMappings = new ArrayList<>();
      	final List constructorArgNames = new ArrayList<>();
      	for (ResultMapping resultMapping : resultMap.resultMappings) {
        	//检测或节点
        	//是否包含select和resultMap属性
        	resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
        	resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
        	final String column = resultMapping.getColumn();
        	if (column != null) {
          		//将colum转换成大写,并添加到mappedColumns集合中
          		resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
        	} else if (resultMapping.isCompositeResult()) {
          		for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
            		final String compositeColumn = compositeResultMapping.getColumn();
            		if (compositeColumn != null) {
              			resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
            		}
          		}
        	}
        	//添加属性property到mappedProperties集合中
        	final String property = resultMapping.getProperty();
        	if (property != null) {
          		resultMap.mappedProperties.add(property);
        	}
        	//检测当前resultMapping是否包含CONSTRUCTOR标志
        	if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          		//添加resultMapping到constructorResultMappings中
          		resultMap.constructorResultMappings.add(resultMapping);
          		//添加属性(constructor节点的name属性)到constructorArgNames中
          		if (resultMapping.getProperty() != null) {
            		constructorArgNames.add(resultMapping.getProperty());
          		}
        	} else {
          		//添加resultMapping到propertyResultMappings中
          		resultMap.propertyResultMappings.add(resultMapping);
        	}
        	if (resultMapping.getFlags().contains(ResultFlag.ID)) {
          		//添加resultMapping到idResultMappings中
          		resultMap.idResultMappings.add(resultMapping);
        	}
      	}
      	if (resultMap.idResultMappings.isEmpty()) {
        	resultMap.idResultMappings.addAll(resultMap.resultMappings);
      	}
      	if (!constructorArgNames.isEmpty()) {
        	//获取构造方法参数列表
        	final List actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
        	if (actualArgNames == null) {
          		throw new BuilderException("Error in result map '" + resultMap.id
              		+ "'. Failed to find a constructor in '"
              		+ resultMap.getType().getName() + "' by arg names " + constructorArgNames
              		+ ". There might be more info in debug log.");
        	}
        	resultMap.constructorResultMappings.sort((o1, o2) -> {
          		int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
          		int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
          		return paramIdx1 - paramIdx2;
        	});
      	}
      	//将以下这些集合变为不可修改集合
      	resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
      	resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
      	resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
      	resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
      	resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
      	return resultMap;
    }

  以上代码主要做的事情就是将ResultMapping实例及属性分别存储到不同的集合中,仅此而已。ResultMap中定义了五种不同的集合:

集合名称用途
mappedColumns用于存储 、 节点column属性
mappedProperties用于存储 节点的property属性,或 和 节点的name属性
idResultMappings用于存储 节点对应的ResultMapping对象
propertyResultMappings用于存储 节点对应的ResultMapping对象
constructorResultMappings用于存储 和 节点对应的ResultMapping对象
2.4 解析< sql >节点

  节点用来定义一些可重用的SQL语句片段,比如表名,或表的列名等。在映射文件中,我们可以通过节点引用节点定义的内容。示例:

	
		 article
	
	以及等。这几个节点中存储的是相同的内容,都是SQL语句,所以这几个节点的解析过程也是相同的。

  在进行代码分析之前,这里需要特别说明一下:为了避免和节点混淆,同时也为了描述方便,这里把