该系列文档是本人在学习 Mybatis 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释(Mybatis源码分析 GitHub 地址、Mybatis-Spring 源码分析 GitHub 地址、Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读html
MyBatis 版本:3.5.2java
MyBatis-Spring 版本:2.0.3node
MyBatis-Spring-Boot-Starter 版本:2.1.4git
在MyBatis初始化过程当中,大体会有如下几个步骤:github
建立Configuration
全局配置对象,会往TypeAliasRegistry
别名注册中心添加Mybatis须要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver
spring
加载mybatis-config.xml
配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会造成相应的对象并保存到Configuration全局配置对象中sql
构建DefaultSqlSessionFactory
对象,经过它能够建立DefaultSqlSession
对象,MyBatis中SqlSession
的默认实现类数据库
由于整个初始化过程涉及到的代码比较多,因此拆分红了四个模块依次对MyBatis的初始化进行分析:apache
因为在MyBatis的初始化过程当中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,因此才分红了后面三个模块,逐步分析,这样便于理解缓存
在上一个模块已经分析了是如何解析mybatis-config.xml
配置文件的,在最后如何解析<mapper />
标签的尚未进行分析,这个过程稍微复杂一点,由于须要解析Mapper接口以及它的XML映射文件,让咱们一块儿来看看这个解析过程
解析XML映射文件生成的对象主要以下图所示:
主要包路径:org.apache.ibatis.builder、org.apache.ibatis.mapping
主要涉及到的类:
org.apache.ibatis.builder.xml.XMLConfigBuilder
:根据配置文件进行解析,开始Mapper接口与XML映射文件的初始化,生成Configuration全局配置对象org.apache.ibatis.binding.MapperRegistry
:Mapper接口注册中心,将Mapper接口与其动态代理对象工厂进行保存,这里咱们解析到的Mapper接口须要往其进行注册org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
:解析Mapper接口,主要是解析接口上面注解,其中加载XML映射文件内部会调用XMLMapperBuilder
类进行解析org.apache.ibatis.builder.xml.XMLMapperBuilder
:解析XML映射文件org.apache.ibatis.builder.xml.XMLStatementBuilder
:解析XML映射文件中的Statement配置(<select /> <update /> <delete /> <insert />
标签)org.apache.ibatis.builder.MapperBuilderAssistant
:Mapper构造器小助手,用于建立ResultMapping、ResultMap和MappedStatement对象org.apache.ibatis.mapping.ResultMapping
:保存<resultMap />
标签的子标签相关信息,也就是 Java Type 与 Jdbc Type 的映射信息org.apache.ibatis.mapping.ResultMap
:保存了<resultMap />
标签的配置信息以及子标签的全部信息org.apache.ibatis.mapping.MappedStatement
:保存了解析<select /> <update /> <delete /> <insert />
标签内的SQL语句所生成的全部信息咱们回顾上一个模块,在org.apache.ibatis.builder.xml.XMLConfigBuilder
中会解析mybatis-config.xml配置文件中的<mapper />
标签,调用其parse()
->parseConfiguration(XNode root)
->mapperElement(XNode parent)
方法,那么咱们来看看这个方法,代码以下:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { // <0> 遍历子节点 for (XNode child : parent.getChildren()) { // <1> 若是是 package 标签,则扫描该包 if ("package".equals(child.getName())) { // 得到包名 String mapperPackage = child.getStringAttribute("name"); // 添加到 configuration 中 configuration.addMappers(mapperPackage); } else { // 若是是 mapper 标签 // 得到 resource、url、class 属性 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // <2> 使用相对于类路径的资源引用 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); // 得到 resource 的 InputStream 对象 InputStream inputStream = Resources.getResourceAsStream(resource); // 建立 XMLMapperBuilder 对象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 执行解析 mapperParser.parse(); // <3> 使用彻底限定资源定位符(URL) } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 得到 url 的 InputStream 对象 InputStream inputStream = Resources.getUrlAsStream(url); // 建立 XMLMapperBuilder 对象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,configuration.getSqlFragments()); // 执行解析 mapperParser.parse(); // <4> 使用映射器接口实现类的彻底限定类名 } else if (resource == null && url == null && mapperClass != null) { // 得到 Mapper 接口 Class<?> mapperInterface = Resources.classForName(mapperClass); // 添加到 configuration 中 configuration.addMapper(mapperInterface); } else { throw new BuilderException( "A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
遍历<mapper />
标签的子节点
若是是<package />
子节点,则获取package
属性,对该包路径下的Mapper接口进行解析
否的的话,经过子节点的resource
属性或者url
属性解析该映射文件,或者经过class
属性解析该Mapper接口
一般咱们是直接配置一个包路径,这里就查看上面第1
种对Mapper接口进行解析的方式,第2
种的解析方式其实在第1
种方式都会涉及到,它只是抽取出来了,那么咱们就直接看第1
种方式
首先将package
包路径添加到Configuration
全局配置对象中,也就是往其内部的MapperRegistry
注册表进行注册,调用它的MapperRegistry
的addMappers(String packageName)
方法进行注册
咱们来看看在MapperRegistry注册表中是如何解析的,在以前文档的Binding模块中有讲到过这个类,该方法以下:
public class MapperRegistry { public void addMappers(String packageName) { addMappers(packageName, Object.class); } /** * 用于扫描指定包中的Mapper接口,并与XML文件进行绑定 * @since 3.2.2 */ public void addMappers(String packageName, Class<?> superType) { // <1> 扫描指定包下的指定类 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); // <2> 遍历,添加到 knownMappers 中 for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } } public <T> void addMapper(Class<T> type) { // <1> 判断,必须是接口。 if (type.isInterface()) { // <2> 已经添加过,则抛出 BindingException 异常 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // <3> 将Mapper接口对应的代理工厂添加到 knownMappers 中 knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the mapper parser. // If the type is already known, it won't try. // <4> 解析 Mapper 的注解配置 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); // 解析 Mapper 接口上面的注解和 Mapper 接口对应的 XML 文件 parser.parse(); // <5> 标记加载完成 loadCompleted = true; } finally { // <6> 若加载未完成,从 knownMappers 中移除 if (!loadCompleted) { knownMappers.remove(type); } } } } }
<1>
首先必须是个接口
<2>
已经在MapperRegistry
注册中心存在,则会抛出异常
<3>
建立一个Mapper接口对应的MapperProxyFactory
动态代理工厂
<4>
【重要!!!】经过MapperAnnotationBuilder
解析该Mapper接口与对应XML映射文件
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
:解析Mapper接口,主要是解析接口上面注解,加载XML文件会调用XMLMapperBuilder类进行解析
咱们先来看看他的构造函数和parse()
解析方法:
public class MapperAnnotationBuilder { /** * 全局配置对象 */ private final Configuration configuration; /** * Mapper 构造器小助手 */ private final MapperBuilderAssistant assistant; /** * Mapper 接口的 Class 对象 */ private final Class<?> type; public MapperAnnotationBuilder(Configuration configuration, Class<?> type) { String resource = type.getName().replace('.', '/') + ".java (best guess)"; this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration; this.type = type; } public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { // 加载该接口对应的 XML 文件 loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); // 解析 Mapper 接口的 @CacheNamespace 注解,建立缓存 parseCache(); // 解析 Mapper 接口的 @CacheNamespaceRef 注解,引用其余命名空间 parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { // 若是不是桥接方法 // 解析方法上面的注解 parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); } private void loadXmlResource() { // Spring may not know the real resource name so we check a flag // to prevent loading again a resource twice // this flag is set at XMLMapperBuilder#bindMapperForNamespace if (!configuration.isResourceLoaded("namespace:" + type.getName())) { String xmlResource = type.getName().replace('.', '/') + ".xml"; // #1347 InputStream inputStream = type.getResourceAsStream("/" + xmlResource); if (inputStream == null) { // Search XML mapper that is not in the module but in the classpath. try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e2) { // ignore, resource is not required } } if (inputStream != null) { // 建立 XMLMapperBuilder 对象 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); // 解析该 XML 文件 xmlParser.parse(); } } } }
在构造函数中,会建立一个MapperBuilderAssistant
对象,Mapper 构造器小助手,用于建立XML映射文件中对应相关对象
parse()
方法,用于解析Mapper接口:
获取Mapper接口的名称,例如interface xxx.xxx.xxx
,根据Configuration全局配置对象判断该Mapper接口是否被解析过
没有解析过则调用loadXmlResource()
方法解析对应的XML映射文件
而后解析接口的@CacheNamespace和@CacheNamespaceRef注解,再依次解析方法上面的MyBatis相关注解
注解的相关解析这里就不讲述了,由于咱们一般都是使用XML映射文件,逻辑没有特别复杂,都在MapperAnnotationBuilder
中进行解析,感兴趣的小伙伴能够看看😈😈😈
loadXmlResource()
方法,解析Mapper接口对应的XML映射文件:
namespace:xxx.xxx.xxx
是否在已加载的资源中xxx/xxx/xxx.xml
文件流,与接口名称对应XMLMapperBuilder
对象,调用其parse()
方法解析该XML映射文件那么接下来咱们来看看XMLMapperBuilder是如何解析XML映射文件的
org.apache.ibatis.builder.xml.XMLMapperBuilder
:解析XML映射文件
继承org.apache.ibatis.builder.BaseBuilder
抽象类,该基类提供了类型转换以及一些其余的工具方法,比较简单,这里就不作展述了
public class XMLMapperBuilder extends BaseBuilder { /** * 基于 Java XPath 解析器 */ private final XPathParser parser; /** * Mapper 构造器助手 */ private final MapperBuilderAssistant builderAssistant; /** * 可被其余语句引用的可重用语句块的集合,实际上就是 Configuration 全局配置中的 sqlFragments * * 例如:<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql> * <sql />可能在不少地方被引用 */ private final Map<String, XNode> sqlFragments; /** * 资源引用的地址,例如:com/aaa/bbb.xml */ private final String resource; public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { this(inputStream, configuration, resource, sqlFragments); this.builderAssistant.setCurrentNamespace(namespace); } public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); // 建立 MapperBuilderAssistant 对象 this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; } }
首先会进入XPathParser
的构造方法,将XML映射文件解析成org.w3c.dom.Document
对象,这里传入了XMLMapperEntityResolver
做为解析实例对象,其中使用到本地的DTD文件
而后建立一个 Mapper 构造器助手 MapperBuilderAssistant
对象
其中一些属性都是从Configuration全局配置对象中获取的,例如:typeAliasRegistry
、typeHandlerRegistry
、sqlFragments
parse()
方法用于解析XML映射文件,在MapperAnnotationBuilder中被调用,代码以下:
public class XMLMapperBuilder extends BaseBuilder { public void parse() { // <1> 判断当前 Mapper 是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // <2> 解析 `<mapper />` 节点 configurationElement(parser.evalNode("/mapper")); // <3> 标记该 Mapper 已经加载过 configuration.addLoadedResource(resource); // <4> 绑定 Mapper bindMapperForNamespace(); } // <5> 解析待定的 <resultMap /> 节点 parsePendingResultMaps(); // <6> 解析待定的 <cache-ref /> 节点 parsePendingCacheRefs(); // <7> 解析待定的 SQL 语句的节点 parsePendingStatements(); } }
<1>
根据Configuration全局配置判断当前XML映射文件是否已经加载过,例如resource为:xxx/xxx/xxx.xml
<2>
解析 <mapper />
节点,也就是解析整个的XML映射文件,在下面的configurationElement
方法中讲解
<3>
标记该XML映射文件已经加载过,往Configuration全局配置添加该字段文件,例如添加:xxx/xxx/xxx.xml
<4>
绑定 Mapper 到该命名空间,避免在MapperAnnotationBuilder#loadXmlResource
方法中重复加载该XML映射文件
<5>
解析待定的 <resultMap />
、<cache-ref />
节点以及 Statement 对象,由于咱们配置的这些对象可能还依赖的其余对象,在解析的过程当中这些依赖可能还没解析出来,致使这个对象解析失败,因此先保存在Configuration全局配置对象中,待整个XML映射文件解析完后,再遍历以前解析失败的对象进行初始化,这里就不作详细的讲述了,感兴趣的小伙伴能够看一下
这里咱们来看一下configurationElement(XNode context)
方法是如何解析XML映射文件中的<mapper />
节点
configurationElement(XNode context)
方法就是来解析XML映射文件中咱们定义的SQL相关信息,代码以下:
public class XMLMapperBuilder extends BaseBuilder { private void configurationElement(XNode context) { try { // <1> 得到 namespace 属性 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // <2> 解析 <cache-ref /> 节点 cacheRefElement(context.evalNode("cache-ref")); // <3> 解析 <cache /> 节点 cacheElement(context.evalNode("cache")); // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在未来被移除,这里不会记录。 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // <4> 解析 <resultMap /> 节点 resultMapElements(context.evalNodes("/mapper/resultMap")); // <5> 解析 <sql /> 节点们 sqlElement(context.evalNodes("/mapper/sql")); // <6> 解析 <select /> <insert /> <update /> <delete /> 节点 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); } } }
<1>
得到 namespace
属性,若是 XML 映射文件中定义的 namespace 和接口名称不相等会抛出异常
<2>
解析 <cache-ref />
节点,调用cacheRefElement方法
<3>
解析 <cache />
节点,调用cacheElement方法
<4>
解析 <resultMap />
节点,调用resultMapElements方法
<5>
解析 <sql />
节点们,调用sqlElement方法
<6>
解析 <select /> <insert /> <update /> <delete />
节点,调用buildStatementFromContext方法
cacheRefElement(XNode context)
方法用于解析XML映射文件中的<cache-ref />
节点,代码以下:
private void cacheRefElement(XNode context) { if (context != null) { // <1> 得到指向的 namespace 名字,并添加到 configuration 的 cacheRefMap 中 configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); // <2> 建立 CacheRefResolver 对象 CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { // 执行解析,获取引用的缓存对象到本身这里 cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { configuration.addIncompleteCacheRef(cacheRefResolver); } } }
解析当前XML映射文件的缓存配置,将当前namespace缓存
引用其余的namespace的缓存
造成映射关系保存在Configuration全局配置对象中
获取引用的namespace
的缓存实例,将其设置到MapperBuilderAssistant构造器助手中,在后续构建相关对象时使用
cacheElement(XNode context)
方法用于XML映射文件中的<cache />
节点,代码以下:
private void cacheElement(XNode context) { if (context != null) { // <1> 得到负责存储的 Cache 实现类 String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); // <2> 得到负责过时的 Cache 实现类 String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); // <3> 得到 flushInterval、size、readWrite、blocking 属性 Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", false); boolean blocking = context.getBooleanAttribute("blocking", false); // <4> 得到 Properties 属性 Properties props = context.getChildrenAsProperties(); // <5> 建立 Cache 对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
解析该节点的相关配置,而后经过MapperBuilderAssistant
构造器小助手建立一个Cache缓存实例,添加到Configuration全局配置对象中,并设置到构造器助手中,在后续构建相关对象时使用
resultMapElements(List<XNode> list)
方法用于解析<resultMap />
节点,最后会调用
resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType)
方法逐个解析生成ResultMap对象
总体的流程图:
例如这样配置RresultMap:
<mapper namespace="com.mybatis3.mappers.StudentMapper"> <!-- 学生 --> <resultMap id="StudentResult" type="Student"> <result column="id" property="studentId" jdbcType="INTEGER" /> <result column="name" property="name" jdbcType="VARCHAR" /> <result column="age" property="age" jdbcType="INTEGER" /> <!-- 老师 --> <association property="teacher" javaType="Teacher"> <result column="teacher_id" property="id" jdbcType="INTEGER" /> <result column="teacher_name" property="name" jdbcType="VARCHAR" /> <result column="teacher_age" property="age" jdbcType="INTEGER" /> </association> </resultMap> </mapper>
resultMapElement
方法代码以下:
public class XMLMapperBuilder extends BaseBuilder { private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception { // 获取当前线程的上下文 ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); // <1> 得到 type 属性 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // 得到 type 对应的类 Class<?> typeClass = resolveClass(type); if (typeClass == null) { // 从 enclosingType Class 对象获取该 property 属性的 Class 对象 typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null; // 建立 ResultMapping 集合 List<ResultMapping> resultMappings = new ArrayList<>(); // 添加父 ResultMap 的 ResultMapping 集合 resultMappings.addAll(additionalResultMappings); // <2> 遍历 <resultMap /> 的子节点 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { // <2.1> 处理 <constructor /> 节点 processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { // <2.2> 处理 <discriminator /> 节点 discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { // <2.3> 处理其它节点 List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { // 为添加该 ResultMapping 添加一个 Id 标志 flags.add(ResultFlag.ID); } // 生成对应的 ResultMapping 对象 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } // 得到 id 属性,没有的话自动生成 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); // 得到 extends 属性 String extend = resultMapNode.getStringAttribute("extends"); // 得到 autoMapping 属性 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // <3> 建立 ResultMapResolver 对象,执行解析 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { // 处理 ResultMap 并添加到 Configuration 全局配置中 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } } }
关于<resultMap />
元素的属性配置参考MyBatis官方文档配置说明
resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType)
方法的入参分别是:
当前节点Node的封装,封装成XNode
便于操做
继承的ResultMap所对应的ResultMapping的集合,能够经过extend属性配置继承哪一个ResultMap,没有继承的话就是空集合
所属的ResultMap的类型,例如<resultMap />
中的<association />
也会被解析成ResultMap,那么它的enclosingType就是所属ResultMap的Class对象
处理逻辑:
得到 type 属性,生成该ResultMap对应Class对象,若是没有定义type属性,则多是<association />
标签,尝试从所属ResultMap的Class对象获取property的Class对象,由于<resultMap />
标签中配置的<association />
标签也会解析成一个ResultMap对象
遍历 <resultMap />
的子节点,依次处理
<constructor />
节点,则调用processConstructorElement
方法进行解析,再获取它的子节点生成对应的RequestMapping对象,这些RequestMapping对象会添加ResultFlag.CONSTRUCTOR
标记,若是是<idArg />
标签则再添加一个ResultFlag.ID
标记,这些对象会在实例化类时,注入到构造方法中<discriminator>
节点,则调用processDiscriminatorElement
方法进行解析,建立一个Discriminator选择器对象,用于可使用结果值来决定这个属性使用哪一个ResultMap
,基于<case />
子节点来进行映射buildResultMappingFromContext
方法进行解析,若是是<id />
则添加一个ResultFlag.ID标记,生成对应的RequestMapping对象建立ResultMapResolver
对象,调用其resolve()
方法执行解析,内部调用MapperBuilderAssistant
构造器小助手的addResultMap
来生成ResultMap对象的
上面的2.1
和2.2
并不复杂,感兴趣的小伙伴能够查看相关方法,都已经注释好了😈😈😈,咱们来看下2.3
是如何解析成ResultMapping对象的
buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)
方法将<resultMap />
标签中的子标签解析成RequestMapping对象,代码以下:
public class XMLMapperBuilder extends BaseBuilder { 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"); // 解析 <resultMap /> 标签中的 <association />,<collection />,<case /> 标签,生成 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 属性 Class<?> javaTypeClass = resolveClass(javaType); // typeHandler 属性 Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); // jdbcType 属性 JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); // 经过上面的属性构建一个 ResultMapping 对象 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); } }
依次从该节点中获取相关属性
这里咱们看到nestedResultMap
的获取,若是这个是<association />
,<collection />
或者<case />
,则会调用processNestedResultMappings
方法解析成ResultMap对象,而后返回该对象的id(没有定义会自动生成),这样这个RequestMapping对象就会关联这个ResultMap对象了,这个方法内部也是调用resultMapElement
方法生成ResultMap对象的,能够回过头再看下这个方法
最后经过MapperBuilderAssistant
构造器小助手的buildResultMapping
方法根据这些属性构建一个ResultMapping
对象并返回
整个的ResultMap对象的解析过程到这里就结束了,关于MapperBuilderAssistant
在后续会讲到,接下来咱们来看看<sql />
节点的解析
sqlElement(List<XNode> list)
方法用于解析全部的<sql />
节点,内部调用sqlElement(List<XNode> list, String requiredDatabaseId)
方法,代码以下:
public class XMLMapperBuilder extends BaseBuilder { private void sqlElement(List<XNode> list, String requiredDatabaseId) { // <1> 遍历全部 <sql /> 节点 for (XNode context : list) { // <2> 得到 databaseId 属性 String databaseId = context.getStringAttribute("databaseId"); // <3> 得到完整的 id 属性 String id = context.getStringAttribute("id"); // 设置为 `${namespace}.${id}` 格式 id = builderAssistant.applyCurrentNamespace(id, false); // <4> 判断 databaseId 是否匹配 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { // <5> 添加到 sqlFragments 中 sqlFragments.put(id, context); } } } }
这里仅仅是将该<sql />
节点保存至Map<String, XNode> sqlFragments
对象中(该对象保存与Configuration全局配置对象中),后续解析其余SQL语句中会使用到,例如查询语句中使用了<include />
标签,则须要获取到对应的<sql />
节点将其替换
buildStatementFromContext(List<XNode> list)
方法用于解析<select /> <insert /> <update /> <delete />
节点
内部调用buildStatementFromContext(List<XNode> list, String requiredDatabaseId)
方法逐个解析生成MappedStatement
对象,代码以下:
public class XMLMapperBuilder extends BaseBuilder { private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { // <1> 遍历 <select /> <insert /> <update /> <delete /> 节点 for (XNode context : list) { // <1> 建立 XMLStatementBuilder 对象 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 解析成 MappedStatement 对象 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { // <2> 解析失败,添加到 configuration 中 configuration.addIncompleteStatement(statementParser); } } } }
为该节点建立XMLStatementBuilder
对象,而后调用其parseStatementNode()
解析成MappedStatement
对象,解析过程在下面的XMLStatementBuilder中讲到
org.apache.ibatis.builder.xml.XMLStatementBuilder
:解析XML映射文件中的Statement配置
也就是解析<select /> <insert /> <update /> <delete />
节点,解析过程在parseStatementNode()
方法中
public class XMLStatementBuilder extends BaseBuilder { private final MapperBuilderAssistant builderAssistant; /** * 当前 XML 节点,例如:<select />、<insert />、<update />、<delete /> 标签 */ private final XNode context; /** * 要求的 databaseId */ private final String requiredDatabaseId; public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context) { this(configuration, builderAssistant, context, null); } public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) { super(configuration); this.builderAssistant = builderAssistant; this.context = context; this.requiredDatabaseId = databaseId; } }
parseStatementNode()
方法用于解析 Statement 对应节点,也就是<select /> <update /> <delete /> <insert />
节点,代码以下:
public class XMLStatementBuilder extends BaseBuilder { public void parseStatementNode() { // 得到 id 属性,编号。 String id = context.getStringAttribute("id"); // 得到 databaseId , 判断 databaseId 是否匹配 String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 获取该节点名称 String nodeName = context.getNode().getNodeName(); // <1> 根据节点名称判断 SQL 类型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); // 是否为 Select 语句 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // <2> 是否清空缓存 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); // <3> 是否使用缓存 boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); // <4> 将该节点的子节点 <include /> 转换成 <sql /> 节点 includeParser.applyIncludes(context.getNode()); // 获取参数类型名称 String parameterType = context.getStringAttribute("parameterType"); // <5> 参数类型名称转换成 Java Type Class<?> parameterTypeClass = resolveClass(parameterType); // <6> 得到 lang 对应的 LanguageDriver 对象 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. // <7> 将该节点的子节点 <selectKey /> 解析成 SelectKeyGenerator 生成器 processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); /* * <8> * 1. 若是上面存在 <selectKey /> 子节点,则获取上面对其解析后生成的 SelectKeyGenerator * 2. 不然判断该节点是否配置了 useGeneratedKeys 属性为 true 而且是 插入语句,则使用 Jdbc3KeyGenerator */ if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // <9> 建立对应的 SqlSource 对象,保存了该节点下 SQL 相关信息 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); // <10> 得到 Statement 类型,默认 PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); // <11> 得到返回结果类型名称 String resultType = context.getStringAttribute("resultType"); // 获取返回结果的 Java Type Class<?> resultTypeClass = resolveClass(resultType); // 获取 resultMap String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } // 对应的 java 属性,结合 useGeneratedKeys 使用 String keyProperty = context.getStringAttribute("keyProperty"); // 对应的 column 列名,结合 useGeneratedKeys 使用 String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); // <12> builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } }
解析方法有点长,咱们一步一步来看
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
flushCache
属性,是否清空缓存,非查询语句默认都是trueuseCache
属性,是否开启缓存,查询语句默认为trueXMLIncludeTransformer
对象,调用其applyIncludes
方法将<include />
转换成<sql />
节点,大体逻辑就是从sqlFragments
(前面已经将全部的<sql />
节点进行解析存放在其中了)获取对应的<sql />
节点,而后替换<include />
节点,具体的转换过程这里就不讲述了,没有特别复杂,感兴趣的小伙伴能够查看相关方法,都已经注释好了😈😈😈parameterType
属性,参数类型,转换成Class对象lang
属性,LanguageDriver语言驱动器,默认为XMLLanguageDriver
<selectKey />
子节点解析成 SelectKeyGenerator 生成器,用于生成一个key设置到返回对象中,在processSelectKeyNodes
方法中能够看到,该过程也会生成一个MappedStatement
对象,生成的对象的 id 为 statementId+'!selectKey'
useGeneratedKeys
属性,获取 SelectKeyGenerator 生成器,若是第7
步没有生成才会进入这里,直接返回Jdbc3KeyGenerator
单例XMLLanguageDriver
语言驱动建立SqlSource
对象,经过这个对象能够获取到对应的SQL语句,在后面的《MyBatis初始化之SQL初始化》分析该建立过程statementType
属性,Statement类型,默认PREPARED
timeout
、resultMap
、keyProperty
、keyColumn
等属性,其中配置的resultType也会转换成ResultMap对象MapperBuilderAssistant
构造器小助手根据这些属性信息构建一个MappedStatement对象org.apache.ibatis.builder.MapperBuilderAssistant
:Mapper构造器小助手,在前面整个XML映射文件的解析过程当中,所须要建立ResultMapping、ResultMap和MappedStatement对象都是经过这个助手来建立的,那么咱们来看看它提供了哪些功能
public class MapperBuilderAssistant extends BaseBuilder { /** * 当前 Mapper 命名空间 */ private String currentNamespace; /** * 资源引用的地址 * 解析Mapper接口:xxx/xxx/xxx.java (best guess) * 解析Mapper映射文件:xxx/xxx/xxx.xml */ private final String resource; /** * 当前 Cache 对象 */ private Cache currentCache; /** * 是否未解析成功 Cache 引用 */ private boolean unresolvedCacheRef; // issue #676 public MapperBuilderAssistant(Configuration configuration, String resource) { super(configuration); ErrorContext.instance().resource(resource); this.resource = resource; } }
😈 这个小助手是为了 XMLMapperBuilder 和 MapperAnnotationBuilder 都能调用到一些公用方法
前面在XMLMapperBuilder的cacheRefElement方法解析<cache-ref />
节点的过程当中有调用这个方法,用来设置当前Cache缓存实例所引用的那个对象,代码以下:
public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { unresolvedCacheRef = true; // 标记未解决 // <1> 得到 Cache 对象 Cache cache = configuration.getCache(namespace); // 得到不到,抛出 IncompleteElementException 异常 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); } }
入参namespace
:所引用的缓存所在的namespace
根据该namespace
获取到其缓存实例对象,而后设置为当前须要使用的缓存实例
前面在XMLMapperBuilder的cacheElement方法解析<cache />
节点的过程当中有调用这个方法,用来建立一个Cache缓存实例,代码以下:
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { // <1> 建立 Cache 对象 // 缓存实例默认为 PerpetualCache 类型,Cache 装饰器默认为 LruCache 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(); // <2> 添加到 configuration 的 caches 中 configuration.addCache(cache); // <3> 赋值给 currentCache currentCache = cache; return cache; }
根据节点中的相关信息经过CacheBuilder
构造器建立一个缓存实例(被装饰的Cache实例),如何构建的代码有点长,这里就不讲述了,感兴趣的小伙伴能够查看相关方法,都已经注释好了😈😈😈
将缓存实例添加到Configuration
全局对象中
设置为当前须要使用的缓存实例
前面在XMLMapperBuilder的resultMapElement方法调用的buildResultMappingFromContext方法中有调用这个方法,用来建立一个RequestMapping对象,代码以下:
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) { // <1> 解析对应的 Java Type Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); // 解析对应的 TypeHandler ,通常不会设置 TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); List<ResultMapping> composites; // <2> 解析组合字段名称成 ResultMapping 集合,涉及「关联的嵌套查询」 if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) { composites = Collections.emptyList(); } else { // RequestMapping 关联了子查询,若是 column 配置了多个则一一再建立 RequestMapping 对象 composites = parseCompositeColumnName(column); } // <3> 建立 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(); }
入参有点多,不过根据名称能够知道其意思,大体逻辑以下:
解析对应的 Java Type 和 TypeHandler 的 Class 对象
若是嵌套的子查询存在组合字段,则一一解析成 ResultMapping 对象,例如须要在返回的结果集中取多个列做为嵌套查询的入参,那么你须要配置多个映射关系
例如子查询的入参对象有两个属性,分别是name和age,而上一层查询从数据库返回的列名是studentName和studentAge,那么你须要在嵌套查询配置column属性为:{name=studentName,age=studentAge},否则没有映射关系没法设置子查询的入参,这样就会为该属性建立两个ResultMapping添加到composites
集合中
调用applyCurrentNamespace
方法,拼接命名空间
调用parseMultipleColumnNames
方法,将字符串(以逗号分隔)解析成集合,做用: 默认状况下,在至少一个被映射到属性的列不为空时,子对象才会被建立。
经过ResultMapping.Builder
构建一个ResultMapping对象
前面在XMLMapperBuilder的resultMapElement方法使用ResultMapResolver
生成ResultMap对象时会调用这个方法,用来解析生成ResultMap对象,代码以下:
public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { // <1> 得到 ResultMap 编号,即格式为 `${namespace}.${id}` id = applyCurrentNamespace(id, false); // <2.1> 获取完整的父 ResultMap 属性,即格式为 `${namespace}.${extend}`。从这里的逻辑来看,貌似只能获取本身 namespace 下的 ResultMap 。 extend = applyCurrentNamespace(extend, true); // <2.2> 若是有父类,则将父类的 ResultMap 集合,添加到 resultMappings 中。 if (extend != null) { // <2.2.1> 得到 extend 对应的 ResultMap 对象。若是不存在,则抛出 IncompleteElementException 异常 // 因此说 <resultMap /> 标签若是有继承关系就必须有前后顺序? if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'"); } ResultMap resultMap = configuration.getResultMap(extend); // 获取 extend 的 ResultMap 对象的 ResultMapping 集合,并移除 resultMappings List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); extendedResultMappings.removeAll(resultMappings); // Remove parent constructor if this resultMap declares a constructor. // 判断当前的 resultMappings 是否有构造方法,若是有,则从 extendedResultMappings 移除全部的构造类型的 ResultMapping boolean declaresConstructor = false; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true; break; } } if (declaresConstructor) { extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)); } // 将 extendedResultMappings 添加到 resultMappings 中 resultMappings.addAll(extendedResultMappings); } // <3> 建立 ResultMap 对象 ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator).build(); // <4> 添加到 configuration 中 configuration.addResultMap(resultMap); return resultMap; }
调用applyCurrentNamespace
方法拼接namespace与id,得到ResultMap的惟一编号,格式为 ${namespace}.${id}
得到父ResultMap的惟一编号extend
,格式为${namespace}.${extend}
extend
为null则直接忽略extend
的ResultMapping集合和本身的ResultMapping集合进行合并经过ResultMap.Builder
构建一个ResultMap对象,并添加到Configuration
全局配置中
在XMLStatementBuilder的parseStatementNode方法中会调用该方法,用来构建一个MappedStatement
对象,代码以下:
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { // <1> 若是的指向的 Cache 未解析,抛出异常 if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } // <2> 得到 id 编号,格式为 `${namespace}.${id}` id = applyCurrentNamespace(id, false); // 是否为查询语句 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // <3> 建立 MappedStatement.Builder 对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType) .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId) .lang(lang).resultOrdered(resultOrdered).resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)).cache(currentCache); // <4> 生成 ParameterMap 对象 ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // <5> 建立 MappedStatement 对象 MappedStatement statement = statementBuilder.build(); // <6> 添加到 configuration 中 configuration.addMappedStatement(statement); return statement; }
入参有点多,这里就不一一进行说明了,经过其名称大体能够知道其意思
若是的指向的 Cache 未解析,抛出异常
得到MappedStatement
的惟一编号id
,格式为${namespace}.${id}
建立MappedStatement.Builder
对象
建立ParameterMap
对象,进入getStatementParameterMap
方法能够看到,ParameterMap的Class<?> type
属性设置为入参类型,String id
设置为statementId
<parameterMap />
标签已经被废弃,因此这里不会配置parameterMap
属性
经过MappedStatement.Builder构建一个MappedStatement对象,并添加到Configuration全局配置中
org.apache.ibatis.mapping.ResultMapping
:保存ResultMap相关子节点的信息,也就是 Java Type 与 Jdbc Type 的映射信息
内部定义了Builder构造器,使用构建者模式建立实例对象,有如下属性:
public class ResultMapping { private Configuration configuration; /** * Java 字段 */ private String property; /** * JDBC 列名 */ private String column; /** * Java 类型 */ private Class<?> javaType; /** * JDBC 类型 */ private JdbcType jdbcType; /** * 类型处理器 */ private TypeHandler<?> typeHandler; /** * 对应的 resultMapId * 例如 <resultMap /> 标签中的 <association/> 标签会生成一个 ResultMap 对象,则这个属性对应该 ResultMap 对象的Id */ private String nestedResultMapId; /** * 关联的子查询 Id */ private String nestedQueryId; /** * 不能为 null 的列名 */ private Set<String> notNullColumns; /** * 列名前缀 */ private String columnPrefix; /** * 具备的标记 */ private List<ResultFlag> flags; /** * 关联嵌套查询的属性映射 */ private List<ResultMapping> composites; private String resultSet; private String foreignColumn; private boolean lazy; ResultMapping() { } }
构建过程这里就不列出来了,能够根据注释进行阅读😈,最终会生成以上这些属性
org.apache.ibatis.mapping.ResultMap
:保存了<resultMap />
配置的全部信息
内部定义了Builder构造器,使用了构建者模式建立实例对象,构建的时候进行了相关属性的分类,有如下属性:
public class ResultMap { /** * Configuration 对象 */ private Configuration configuration; /** * ResultMap 对象 */ private String id; /** * 类型 */ private Class<?> type; /** * ResultMapping 集合 */ private List<ResultMapping> resultMappings; /** * ID ResultMapping 集合 * * 当 idResultMappings 为空时,使用 {@link #resultMappings} 赋值 */ private List<ResultMapping> idResultMappings; /** * 构造方法的入参 ResultMapping 集合 * 根据参数名称已经排序好了 * * 和 {@link #propertyResultMappings} 不存在相同元素 */ private List<ResultMapping> constructorResultMappings; /** * 属性 ResultMapping 集合 */ private List<ResultMapping> propertyResultMappings; /** * 数据库的字段集合(所有大写) */ private Set<String> mappedColumns; /** * Java 对象的属性集合 */ private Set<String> mappedProperties; /** * Discriminator 选择器对象 */ private Discriminator discriminator; /** * 是否有内嵌的 ResultMap */ private boolean hasNestedResultMaps; /** * 是否有嵌套关联的子查询 */ private boolean hasNestedQueries; /** * 是否开启自动匹配 * * 若是设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性 * autoMappingBehavior。默认值为:unset。 */ private Boolean autoMapping; private ResultMap() { } }
构建过程这里就不列出来了,能够根据注释进行阅读😈,最终会生成以上这些属性
org.apache.ibatis.mapping.MappedStatement
:保存了解析<select /> <update /> <delete /> <insert />
标签内的SQL语句所生成的全部信息
内部定义了Builder构造器,使用了构建者模式构建对象,有如下属性:
public final class MappedStatement { /** * XML 映射文件路径,例如:xxx/xxx/xxx.xml */ private String resource; /** * 全局配置对象 */ private Configuration configuration; /** * 惟一编号:`${namespace}.${id}` */ private String id; /** * 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值 * 默认值为未设置(unset)(依赖驱动) */ private Integer fetchSize; /** * 这个设置是在抛出异常以前,驱动程序等待数据库返回请求结果的秒数 * 默认值为未设置(unset)(依赖数据库驱动) */ private Integer timeout; /** * Statement 的类型:STATEMENT PREPARED CALLABLE,默认 PREPARED * 分别对应:Statement PreparedStatement CallableStatement */ private StatementType statementType; private ResultSetType resultSetType; /** * SQL 相关信息 */ private SqlSource sqlSource; /** * 缓存对象 */ private Cache cache; private ParameterMap parameterMap; /** * ResultMap对象 * 配置多个时须要加上 namespace 并以逗号分隔 */ private List<ResultMap> resultMaps; /** * 是否清空缓存 */ private boolean flushCacheRequired; /** * 是否使用缓存 */ private boolean useCache; /** * 这个设置仅针对嵌套结果 select 语句,默认值:false * 若是为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用 * 这就使得在获取嵌套结果集的时候不至于内存不够用 */ private boolean resultOrdered; /** * SQL 语句类型 */ private SqlCommandType sqlCommandType; /** * key 的生成器 */ private KeyGenerator keyGenerator; /** * key 的生成器的 Java 属性 */ private String[] keyProperties; /** * key 的生成器的 column 列名 */ private String[] keyColumns; /** * 是否有内嵌的 ResultMap */ private boolean hasNestedResultMaps; /** * 数据库表示 */ private String databaseId; /** * 日志对象 */ private Log statementLog; /** * 语言驱动,默认为XMLLanguageDriver */ private LanguageDriver lang; /** * 这个设置仅适用于多结果集的状况 * 它将列出语句执行后返回的结果集并赋予每一个结果集一个名称,多个名称之间以逗号分隔 */ private String[] resultSets; MappedStatement() { // constructor disabled } }
构建过程这里就不列出来了,能够根据注释进行阅读😈,最终会生成以上这些属性
其中SqlSource
是经过XMLLanguageDriver
语言驱动建立的,能够回到XmlStatementBuilder的parseStatementNode()方法看看,在后面的《MyBatis初始化之SQL初始化》分析整个建立过程
本分分析了MyBatis在初始化时加载Mapper接口与XML映射文件的整个过程
在XMLConfigBuilder
中将用户配置的Mapper接口所在包路径package
添加到MapperRegistry
注册表中
在MapperRegistry
注册表中会对包下的全部Mapper接口进行解析,每一个接口都会建立对应的MapperProxyFactory
动态代理对象工厂,并保存,也会经过MapperAnnotationBuilder
对该接口进行解析,解析过程:
namespace
属性关联,因此XML映射文件的名称要与Mapper接口的名称保持一致,配置的namespace
属性要与接口的全名保持一致解析XML映射文件的过程当中是在XMLMapperBuilder
中进行的,会使用到MapperBuilderAssistant
小助手用于建立ResultMapping
、ResultMap
和MappedStatement
对象
其中解析<select /> <update /> <delete /> <insert />
标签的解析过程又在XMLStatementBuilder
对象中进行
在XMLStatementBuilder
解析上面标签的时候须要经过XMLLanguageDriver
语言驱动建立SqlSource
对象,这个过程涉及到的篇幅有点多,会在接下来的《MyBatis初始化(三)之SQL初始化(上)》中开始分析
最终全部的MyBatis配置、Mapper接口和XML映射文件生成的相应对象都保存在了Configuration全局配置对象中,那么接下来咱们来看看SQL语句在MyBatis中是如何初始化的
参考文章:芋道源码《精尽 MyBatis 源码分析》