該系列文檔是本人在學習 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 源碼分析》