上一篇咱們講解到mapperElement方法用來解析mapper,咱們這篇文章具體來看看mapper.xml的解析過程java
mappers 標籤下有許多 mapper 標籤,每個 mapper 標籤中配置的都是一個獨立的映射配置文件的路徑,配置方式有如下幾種。node
<mappers> <mapper class="org.mybatis.mappers.UserMapper"/> <mapper class="org.mybatis.mappers.ProductMapper"/> <mapper class="org.mybatis.mappers.ManagerMapper"/> </mappers>
注意:這種方式必須保證接口名(例如UserMapper)和xml名(UserMapper.xml)相同,還必須在同一個包中。由於是經過獲取mapper中的class屬性,拼接上.xml來讀取UserMapper.xml,若是xml文件名不一樣或者不在同一個包中是沒法讀取到xml的。redis
<mappers> <mapper resource="org/mybatis/mappers/UserMapper.xml"/> <mapper resource="org/mybatis/mappers/ProductMapper.xml"/> <mapper resource="org/mybatis/mappers/ManagerMapper.xml"/> </mappers>
注意:這種方式不用保證同接口同包同名。可是要保證xml中的namespase和對應的接口名相同。sql
<mappers> <mapper url="file:///var/mappers/UserMapper.xml"/> <mapper url="file:///var/mappers/ProductMapper.xml"/> <mapper url="file:///var/mappers/ManagerMapper.xml"/> </mappers>
<mappers>
<package name="org.mybatis.mappers"/>
</mappers>
這種方式和第一種方式要求一致,保證接口名(例如UserMapper)和xml名(UserMapper.xml)相同,還必須在同一個包中。apache
注意:以上全部的配置都要保證xml中的namespase和對應的接口名相同。緩存
咱們以packae屬性爲例詳細分析一下:安全
接上一篇文章最後部分,咱們來看看mapperElement方法:mybatis
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //包掃描的形式 if ("package".equals(child.getName())) { // 獲取 <package> 節點中的 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."); } } } } }
在 MyBatis 中,共有四種加載映射文件或信息的方式。第一種是從文件系統中加載映射文件;第二種是經過 URL 的方式加載和解析映射文件;第三種是經過 mapper 接口加載映射信息,映射信息能夠配置在註解中,也能夠配置在映射文件中。最後一種是經過包掃描的方式獲取到某個包下的全部類,並使用第三種方式爲每一個類解析映射信息。app
咱們先看下以packae掃描的形式,看下configuration.addMappers(mapperPackage)方法dom
public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); }
咱們看一下MapperRegistry的addMappers方法:
1 public void addMappers(String packageName) { 2 //傳入包名和Object.class類型 3 this.addMappers(packageName, Object.class); 4 } 5 6 public void addMappers(String packageName, Class<?> superType) { 7 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil(); 8 /* 9 * 查找包下的父類爲 Object.class 的類。 10 * 查找完成後,查找結果將會被緩存到resolverUtil的內部集合中。上一篇文章咱們已經看過這部分的源碼,再也不累述了 11 */ 12 resolverUtil.find(new IsA(superType), packageName); 13 // 獲取查找結果 14 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 15 Iterator i$ = mapperSet.iterator(); 16 17 while(i$.hasNext()) { 18 Class<?> mapperClass = (Class)i$.next(); 19 //咱們具體看這個方法 20 this.addMapper(mapperClass); 21 } 22 23 }
其實就是經過 VFS(虛擬文件系統)獲取指定包下的全部文件的Class,也就是全部的Mapper接口,而後遍歷每一個Mapper接口進行解析,接下來就和第一種配置方式(接口信息進行配置)同樣的流程了,接下來咱們來看看 基於 XML 的映射文件的解析過程,能夠看到先建立一個XMLMapperBuilder,再調用其parse()方法:
1 public void parse() { 2 // 檢測映射文件是否已經被解析過 3 if (!configuration.isResourceLoaded(resource)) { 4 // 解析 mapper 節點 5 configurationElement(parser.evalNode("/mapper")); 6 // 添加資源路徑到「已解析資源集合」中 7 configuration.addLoadedResource(resource); 8 // 經過命名空間綁定 Mapper 接口 9 bindMapperForNamespace(); 10 } 11 12 parsePendingResultMaps(); 13 parsePendingCacheRefs(); 14 parsePendingStatements(); 15 }
咱們重點關注第5行和第9行的邏輯,也就是configurationElement和bindMapperForNamespace方法
在 MyBatis 映射文件中,能夠配置多種節點。好比 <cache>,<resultMap>,<sql> 以及 <select | insert | update | delete> 等。下面咱們來看一個映射文件配置示例。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="mapper.EmployeeMapper"> <cache/> <resultMap id="baseMap" type="entity.Employee"> <result property="id" column="id" jdbcType="INTEGER"></result> <result property="name" column="name" jdbcType="VARCHAR"></result> </resultMap> <sql id="table"> employee </sql> <select id="getAll" resultMap="baseMap"> select * from <include refid="table"/> WHERE id = #{id} </select> <!-- <insert|update|delete/> --> </mapper>
接着來看看configurationElement解析mapper.xml中的內容。
1 private void configurationElement(XNode context) { 2 try { 3 // 獲取 mapper 命名空間,如 mapper.EmployeeMapper 4 String namespace = context.getStringAttribute("namespace"); 5 if (namespace == null || namespace.equals("")) { 6 throw new BuilderException("Mapper's namespace cannot be empty"); 7 } 8 9 // 設置命名空間到 builderAssistant 中 10 builderAssistant.setCurrentNamespace(namespace); 11 12 // 解析 <cache-ref> 節點 13 cacheRefElement(context.evalNode("cache-ref")); 14 15 // 解析 <cache> 節點 16 cacheElement(context.evalNode("cache")); 17 18 // 已廢棄配置,這裏不作分析 19 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 20 21 // 解析 <resultMap> 節點 22 resultMapElements(context.evalNodes("/mapper/resultMap")); 23 24 // 解析 <sql> 節點 25 sqlElement(context.evalNodes("/mapper/sql")); 26 27 // 解析 <select>、<insert>、<update>、<delete> 節點 28 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 29 } catch (Exception e) { 30 throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); 31 } 32 }
接下來咱們就對其中關鍵的方法進行詳細分析
MyBatis 提供了1、二級緩存,其中一級緩存是 SqlSession 級別的,默認爲開啓狀態。二級緩存配置在映射文件中,使用者須要顯示配置才能開啓。以下:
<cache/>
也可使用第三方緩存
<cache type="org.mybatis.caches.redis.RedisCache"/>
其中有一些屬性能夠選擇
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
下面咱們來分析一下緩存配置的解析邏輯,以下:
private void cacheElement(XNode context) throws Exception { if (context != null) { // 獲取type屬性,若是type沒有指定就用默認的PERPETUAL(早已經註冊過的別名的PerpetualCache) String type = context.getStringAttribute("type", "PERPETUAL"); // 根據type從早已經註冊的別名中獲取對應的Class,PERPETUAL對應的Class是PerpetualCache.class // 若是咱們寫了type屬性,如type="org.mybatis.caches.redis.RedisCache",這裏將會獲得RedisCache.class Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); //獲取淘汰方式,默認爲LRU(早已經註冊過的別名的LruCache),最近最少使用到的先淘汰 String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> 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); } } public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } else { // 轉換成小寫 String key = string.toLowerCase(Locale.ENGLISH); Class value; // 若是沒有設置type屬性,則這裏傳過來的是PERPETUAL,能從別名緩存中獲取到PerpetualCache.class if (this.TYPE_ALIASES.containsKey(key)) { value = (Class)this.TYPE_ALIASES.get(key); } else { //若是是設置了自定義的type,則在別名緩存中是獲取不到的,直接經過類加載,加載自定義的type,如RedisCache.class value = Resources.classForName(string); } return value; } } catch (ClassNotFoundException var4) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + var4, var4); } }
緩存的構建封裝在 BuilderAssistant 類的 useNewCache 方法中,咱們來看看
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> 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 實例,咱們跟下去看看。
public Cache build() { // 設置默認的緩存類型(PerpetualCache)和緩存裝飾器(LruCache) setDefaultImplementations(); // 經過反射建立緩存 Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); // 僅對內置緩存 PerpetualCache 應用裝飾器 if (PerpetualCache.class.equals(cache.getClass())) { // 遍歷裝飾器集合,應用裝飾器 for (Class<? extends Cache> 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; } private void setDefaultImplementations() { if (this.implementation == null) { //設置默認緩存類型爲PerpetualCache this.implementation = PerpetualCache.class; if (this.decorators.isEmpty()) { this.decorators.add(LruCache.class); } } } private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) { //獲取構造器 Constructor cacheConstructor = this.getBaseCacheConstructor(cacheClass); try { //經過構造器實例化Cache return (Cache)cacheConstructor.newInstance(id); } catch (Exception var5) { throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + var5, var5); } }
如上就建立好了一個Cache的實例,而後把它添加到Configuration中,而且設置到currentCache屬性中,這個屬性後面還要使用,也就是Cache實例後面還要使用,咱們後面再看。
resultMap 主要用於映射結果。經過 resultMap 和自動映射,可讓 MyBatis 幫助咱們完成 ResultSet → Object 的映射。下面開始分析 resultMap 配置的解析過程。
private void resultMapElements(List<XNode> list) throws Exception { // 遍歷 <resultMap> 節點列表 for (XNode resultMapNode : list) { try { // 解析 resultMap 節點 resultMapElement(resultMapNode); } catch (IncompleteElementException e) { } } } private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList()); } private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); // 獲取 id 和 type 屬性 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // 獲取 extends 和 autoMapping String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // 獲取 type 屬性對應的類型 Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; //建立ResultMapping集合,對應resultMap子節點的id和result節點 List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(additionalResultMappings); // 獲取並遍歷 <resultMap> 的子節點列表 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<ResultFlag>(); if ("id".equals(resultChild.getName())) { // 添加 ID 到 flags 集合中 flags.add(ResultFlag.ID); } // 解析 id 和 result 節點,將id或result節點生成相應的 ResultMapping,將ResultMapping添加到resultMappings集合中 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } //建立ResultMapResolver對象 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { // 根據前面獲取到的信息構建 ResultMap 對象 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
解析 id 和 result 節點
在 <resultMap> 節點中,子節點 <id> 和 <result> 都是常規配置,比較常見。咱們來看看解析過程
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception { 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 屬性,該屬性出如今 <association> 和 <collection> 節點中。 * 若這兩個節點不包含 resultMap 屬性,則調用 processNestedResultMappings 方法,遞歸調用resultMapElement解析<association> 和 <collection>的嵌套節點,生成resultMap,並返回resultMap.getId(); * 若是包含resultMap屬性,則直接獲取其屬性值,這個屬性值對應一個resultMap節點 */ String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.<ResultMapping>emptyList())); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); Class<?> javaTypeClass = resolveClass(javaType); Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); // 構建 ResultMapping 對象 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }
看processNestedResultMappings解析<association> 和 <collection> 節點中的子節點,並返回ResultMap.id
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception { if (("association".equals(context.getName()) || "collection".equals(context.getName()) || "case".equals(context.getName())) && context.getStringAttribute("select") == null) { ResultMap resultMap = this.resultMapElement(context, resultMappings); return resultMap.getId(); } else { return null; } }
只要此節點是(association或者collection)而且select爲空,就說明是嵌套查詢,那若是select不爲空呢?那說明是延遲加載此節點的信息,並不屬於嵌套查詢,可是有可能有多個association或者collection,有一個設置爲延遲加載也就是select屬性不爲空,有一個沒有設置延遲加載,那說明resultMap中有嵌套查詢的ResultMapping,也有延遲加載的ResultMapping,這個在後面結果集映射時會用到。
下面以 <association> 節點爲例,演示該節點的兩種配置方式,分別以下:
第一種配置方式是經過 resultMap 屬性引用其餘的 <resultMap> 節點,配置以下:
<resultMap id="articleResult" type="Article"> <id property="id" column="id"/> <result property="title" column="article_title"/> <!-- 引用 authorResult,此時爲嵌套查詢 --> <association property="article_author" column="article_author_id" javaType="Author" resultMap="authorResult"/> <!-- 引用 authorResult,此時爲延遲查詢 --> <association property="article_author" column="article_author_id" javaType="Author" select="authorResult"/> </resultMap> <resultMap id="authorResult" type="Author"> <id property="id" column="author_id"/> <result property="name" column="author_name"/> </resultMap>
第二種配置方式是採起 resultMap 嵌套的方式進行配置,以下:
<resultMap id="articleResult" type="Article"> <id property="id" column="id"/> <result property="title" column="article_title"/> <!-- resultMap 嵌套 --> <association property="article_author" javaType="Author"> <id property="id" column="author_id"/> <result property="name" column="author_name"/> </association> </resultMap>
第二種配置,<association> 的子節點是一些結果映射配置,這些結果配置最終也會被解析成 ResultMap。
下面分析 ResultMapping 的構建過程。
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) { // resultType:即 <resultMap type="xxx"/> 中的 type 屬性 // property:即 <result property="xxx"/> 中的 property 屬性 Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); List<ResultMapping> 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<ResultFlag>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); } private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) { if (javaType == null && property != null) { try { //獲取ResultMap中的type屬性的元類,如<resultMap id="user" type="java.model.User"/> 中User的元類 MetaClass metaResultType = MetaClass.forClass(resultType, this.configuration.getReflectorFactory()); //<result property="name" javaType="String"/>,若是result中沒有設置javaType,則獲取元類屬性對那個的類型 javaType = metaResultType.getSetterType(property); } catch (Exception var5) { ; } } if (javaType == null) { javaType = Object.class; } return javaType; } public ResultMapping build() { resultMapping.flags = Collections.unmodifiableList(resultMapping.flags); resultMapping.composites = Collections.unmodifiableList(resultMapping.composites); resolveTypeHandler(); validate(); return resultMapping; }
咱們來看看ResultMapping類
public class ResultMapping { private Configuration configuration; private String property; private String column; private Class<?> javaType; private JdbcType jdbcType; private TypeHandler<?> typeHandler; private String nestedResultMapId; private String nestedQueryId; 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() { } //略 }
咱們看到ResultMapping中有屬性nestedResultMapId表示嵌套查詢和nestedQueryId表示延遲查詢
ResultMapping就是和ResultMap中子節點id和result對應
<id column="wi_id" jdbcType="INTEGER" property="id" /> <result column="warrant_no" jdbcType="String" jdbcType="CHAR" property="warrantNo" />
ResultMap 對象構建
前面的分析咱們知道了<id>,<result> 等節點最終都被解析成了 ResultMapping。而且封裝到了resultMappings集合中,緊接着要作的事情是構建 ResultMap,關鍵代碼在resultMapResolver.resolve():
public ResultMap resolve() { return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping); } public ResultMap addResultMap( String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> 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<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings()); extendedResultMappings.removeAll(resultMappings); boolean declaresConstructor = false; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true; break; } } if (declaresConstructor) { Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator(); while (extendedResultMappingsIter.hasNext()) { if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) { extendedResultMappingsIter.remove(); } } } resultMappings.addAll(extendedResultMappings); } // 構建 ResultMap ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); // 將建立好的ResultMap加入configuration中 configuration.addResultMap(resultMap); return resultMap; }
咱們先看看ResultMap
public class ResultMap { private String id; private Class<?> type; private List<ResultMapping> resultMappings; //用於存儲 <id> 節點對應的 ResultMapping 對象 private List<ResultMapping> idResultMappings; private List<ResultMapping> constructorResultMappings; //用於存儲 <id> 和 <result> 節點對應的 ResultMapping 對象 private List<ResultMapping> propertyResultMappings; //用於存儲 全部<id>、<result> 節點 column 屬性 private Set<String> mappedColumns; private Discriminator discriminator; private boolean hasNestedResultMaps; private boolean hasNestedQueries; private Boolean autoMapping; private ResultMap() { } //略 }
再來看看經過建造模式構建 ResultMap 實例
public ResultMap build() { if (resultMap.id == null) { throw new IllegalArgumentException("ResultMaps must have an id"); } resultMap.mappedColumns = new HashSet<String>(); resultMap.mappedProperties = new HashSet<String>(); resultMap.idResultMappings = new ArrayList<ResultMapping>(); resultMap.constructorResultMappings = new ArrayList<ResultMapping>(); resultMap.propertyResultMappings = new ArrayList<ResultMapping>(); final List<String> constructorArgNames = new ArrayList<String>(); for (ResultMapping resultMapping : resultMap.resultMappings) { /* * resultMapping.getNestedQueryId()不爲空,表示當前resultMap是中有須要延遲查詢的屬性 * resultMapping.getNestedResultMapId()不爲空,表示當前resultMap是一個嵌套查詢 * 有可能當前ResultMapp既是一個嵌套查詢,又存在延遲查詢的屬性 */ 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); } if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { resultMap.constructorResultMappings.add(resultMapping); 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<String> 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."); } Collections.sort(resultMap.constructorResultMappings, new Comparator<ResultMapping>() { @Override public int compare(ResultMapping o1, ResultMapping 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 實例及屬性分別存儲到不一樣的集合中。
<sql> 節點用來定義一些可重用的 SQL 語句片斷,好比表名,或表的列名等。在映射文件中,咱們能夠經過 <include> 節點引用 <sql> 節點定義的內容。
<sql id="table"> user </sql> <select id="findOne" resultType="Article"> SELECT * FROM <include refid="table"/> WHERE id = #{id} </select>
下面分析一下 sql 節點的解析過程,以下:
private void sqlElement(List<XNode> list) throws Exception { if (configuration.getDatabaseId() != null) { // 調用 sqlElement 解析 <sql> 節點 sqlElement(list, configuration.getDatabaseId()); } // 再次調用 sqlElement,不一樣的是,此次調用,該方法的第二個參數爲 null sqlElement(list, null); } private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception { for (XNode context : list) { // 獲取 id 和 databaseId 屬性 String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); // id = currentNamespace + "." + id id = builderAssistant.applyCurrentNamespace(id, false); // 檢測當前 databaseId 和 requiredDatabaseId 是否一致 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { // 將 <id, XNode> 鍵值對緩存到XMLMapperBuilder對象的 sqlFragments 屬性中,以供後面的sql語句使用 sqlFragments.put(id, context); } } }
<select>、<insert>、<update> 以及 <delete> 等節點統稱爲 SQL 語句節點,其解析過程在buildStatementFromContext方法中:
private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { // 調用重載方法構建 Statement buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { // 建立 XMLStatementBuilder 建造類 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { /* * 解析sql節點,將其封裝到 Statement 對象中,並將解析結果存儲到 configuration 的 mappedStatements 集合中 */ statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
咱們繼續看 statementParser.parseStatementNode();
public void parseStatementNode() { // 獲取 id 和 databaseId 屬性 String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 獲取各類屬性 Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // 經過別名解析 resultType 對應的類型 Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); // 解析 Statement 類型,默認爲 PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); // 解析 ResultSetType ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); // 獲取節點的名稱,好比 <select> 節點名稱爲 select String nodeName = context.getNode().getNodeName(); // 根據節點名稱解析 SqlCommandType SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // 解析 <include> 節點 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); processSelectKeyNodes(id, parameterTypeClass, langDriver); // 解析 SQL 語句 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } /* * 構建 MappedStatement 對象,並將該對象存儲到 Configuration 的 mappedStatements 集合中 */ builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
咱們主要來分析下面幾個重要的方法:
解析 <include> 節點
先來看一個include的例子
<mapper namespace="java.mybaits.dao.UserMapper"> <sql id="table"> user </sql> <select id="findOne" resultType="User"> SELECT * FROM <include refid="table"/> WHERE id = #{id} </select> </mapper>
<include> 節點的解析邏輯封裝在 applyIncludes 中,該方法的代碼以下:
public void applyIncludes(Node source) { Properties variablesContext = new Properties(); Properties configurationVariables = configuration.getVariables(); if (configurationVariables != null) { // 將 configurationVariables 中的數據添加到 variablesContext 中 variablesContext.putAll(configurationVariables); } // 調用重載方法處理 <include> 節點 applyIncludes(source, variablesContext, false); }
繼續看 applyIncludes 方法
private void applyIncludes(Node source, final Properties variablesContext, boolean included) { // 第一個條件分支 if (source.getNodeName().equals("include")) { //獲取 <sql> 節點。 Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext); Properties toIncludeContext = getVariablesContext(source, variablesContext); applyIncludes(toInclude, toIncludeContext, true); if (toInclude.getOwnerDocument() != source.getOwnerDocument()) { toInclude = source.getOwnerDocument().importNode(toInclude, true); } // 將 <select>節點中的 <include> 節點替換爲 <sql> 節點 source.getParentNode().replaceChild(toInclude, source); while (toInclude.hasChildNodes()) { // 將 <sql> 中的內容插入到 <sql> 節點以前 toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude); } /* * 前面已經將 <sql> 節點的內容插入到 dom 中了, * 如今不須要 <sql> 節點了,這裏將該節點從 dom 中移除 */ toInclude.getParentNode().removeChild(toInclude); // 第二個條件分支 } else if (source.getNodeType() == Node.ELEMENT_NODE) { if (included && !variablesContext.isEmpty()) { NamedNodeMap attributes = source.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attr = attributes.item(i); // 將 source 節點屬性中的佔位符 ${} 替換成具體的屬性值 attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext)); } } NodeList children = source.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { // 遞歸調用 applyIncludes(children.item(i), variablesContext, included); } // 第三個條件分支 } else if (included && source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) { // 將文本(text)節點中的屬性佔位符 ${} 替換成具體的屬性值 source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext)); } }
咱們先來看一下 applyIncludes 方法第一次被調用時的狀態,source爲<select> 節點,節點類型:ELEMENT_NODE,此時會進入第二個分支,獲取到獲取 <select> 子節點列表,遍歷子節點列表,將子節點做爲參數,進行遞歸調用applyIncludes ,此時可獲取到的子節點以下:
編號 | 子節點 | 類型 | 描述 |
---|---|---|---|
1 | SELECT * FROM | TEXT_NODE | 文本節點 |
2 | <include refid="table"/> | ELEMENT_NODE | 普通節點 |
3 | WHERE id = #{id} | TEXT_NODE | 文本節點 |
接下來要作的事情是遍歷列表,而後將子節點做爲參數進行遞歸調用。第一個子節點調用applyIncludes方法,source爲 SELECT * FROM 節點,節點類型:TEXT_NODE,進入分支三,沒有${},不會替換,則節點一結束返回,什麼都沒有作。第二個節點調用applyIncludes方法,此時source爲 <include refid="table"/>節點,節點類型:ELEMENT_NODE,進入分支一,經過refid找到 sql 節點,也就是toInclude節點,而後執行source.getParentNode().replaceChild(toInclude, source);,直接將<include refid="table"/>節點的父節點,也就是<select> 節點中的當前<include >節點替換成 <sql> 節點,而後調用toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);,將 <sql> 中的內容插入到 <sql> 節點以前,也就是將user插入到 <sql> 節點以前,如今不須要 <sql> 節點了,最後將該節點從 dom 中移除
建立SqlSource
建立SqlSource在createSqlSource方法中
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); } // -☆- XMLScriptBuilder public SqlSource parseScriptNode() { // 解析 SQL 語句節點 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; // 根據 isDynamic 狀態建立不一樣的 SqlSource if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
繼續跟進parseDynamicTags
/** 該方法用於初始化 nodeHandlerMap 集合,該集合後面會用到 */ private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); } protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<SqlNode>(); NodeList children = node.getNode().getChildNodes(); // 遍歷子節點 for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); //若是節點是TEXT_NODE類型 if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // 獲取文本內容 String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); // 若文本中包含 ${} 佔位符,會被認爲是動態節點 if (textSqlNode.isDynamic()) { contents.add(textSqlNode); // 設置 isDynamic 爲 true isDynamic = true; } else { // 建立 StaticTextSqlNode contents.add(new StaticTextSqlNode(data)); } // child 節點是 ELEMENT_NODE 類型,好比 <if>、<where> 等 } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // 獲取節點名稱,好比 if、where、trim 等 String nodeName = child.getNode().getNodeName(); // 根據節點名稱獲取 NodeHandler,也就是上面註冊的nodeHandlerMap NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } // 處理 child 節點,生成相應的 SqlNode handler.handleNode(child, contents); // 設置 isDynamic 爲 true isDynamic = true; } } return new MixedSqlNode(contents); }
對於if、trim、where等這些動態節點,是經過對應的handler來解析的,以下
handler.handleNode(child, contents);
該代碼用於處理動態 SQL 節點,並生成相應的 SqlNode。下面來簡單分析一下 WhereHandler 的代碼。
/** 定義在 XMLScriptBuilder 中 */ private class WhereHandler implements NodeHandler { public WhereHandler() { } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 調用 parseDynamicTags 解析 <where> 節點 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 建立 WhereSqlNode WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode); // 添加到 targetContents targetContents.add(where); } }
咱們已經將 XML 配置解析了 SqlSource,下面咱們看看MappedStatement的構建。
構建MappedStatement
SQL 語句節點能夠定義不少屬性,這些屬性和屬性值最終存儲在 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) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } // 拼接上命名空間,如 <select id="findOne" resultType="User">,則id=java.mybaits.dao.UserMapper.findOne id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 建立建造器,設置各類屬性 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)) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .resultSetType(resultSetType).useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache);//這裏用到了前面解析<cache>節點時建立的Cache對象,設置到MappedStatement對象裏面的cache屬性中 // 獲取或建立 ParameterMap ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 構建 MappedStatement MappedStatement statement = statementBuilder.build(); // 添加 MappedStatement 到 configuration 的 mappedStatements 集合中 // 經過UserMapper代理對象調用findOne方法時,就能夠拼接UserMapper接口名java.mybaits.dao.UserMapper和findOne方法找到id=java.mybaits.dao.UserMapper的MappedStatement,而後執行對應的sql語句 configuration.addMappedStatement(statement); return statement; }
這裏咱們要注意,MappedStatement對象中有一個cache屬性,將前面解析<cache>節點時建立的Cache對象,設置到MappedStatement對象裏面的cache屬性中,以備後面二級緩存使用,咱們後面專門來說這一塊。
咱們還要注意一個地方,.resultMaps(getStatementResultMaps(resultMap, resultType, id)),設置MappedStatement的resultMaps,咱們來看看是怎麼獲取resultMap的
private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) { //拼接上當前nameSpace resultMap = this.applyCurrentNamespace(resultMap, true); //建立一個集合 List<ResultMap> resultMaps = new ArrayList(); if (resultMap != null) { //經過,分隔字符串,通常resultMap只會是一個,不會使用逗號 String[] resultMapNames = resultMap.split(","); String[] arr$ = resultMapNames; int len$ = resultMapNames.length; for(int i$ = 0; i$ < len$; ++i$) { String resultMapName = arr$[i$]; try { //從configuration中經過resultMapName獲取ResultMap對象加入到resultMaps中 resultMaps.add(this.configuration.getResultMap(resultMapName.trim())); } catch (IllegalArgumentException var11) { throw new IncompleteElementException("Could not find result map " + resultMapName, var11); } } } else if (resultType != null) { ResultMap inlineResultMap = (new org.apache.ibatis.mapping.ResultMap.Builder(this.configuration, statementId + "-Inline", resultType, new ArrayList(), (Boolean)null)).build(); resultMaps.add(inlineResultMap); } return resultMaps; }
從configuration中獲取到ResultMap並設置到MappedStatement中,當查詢結束後,就能夠拿到ResultMap進行結果映射,這個在後面講
映射文件解析完成後,咱們須要經過命名空間將綁定 mapper 接口,看看具體綁定的啥
private void bindMapperForNamespace() { // 獲取映射文件的命名空間 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 根據命名空間解析 mapper 類型 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null) { // 檢測當前 mapper 類是否被綁定過 if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); // 綁定 mapper 類 configuration.addMapper(boundType); } } } } // Configuration public <T> void addMapper(Class<T> type) { // 經過 MapperRegistry 綁定 mapper 類 mapperRegistry.addMapper(type); } // MapperRegistry public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { /* * 將 type 和 MapperProxyFactory 進行綁定,MapperProxyFactory 可爲 mapper 接口生成代理類 */ knownMappers.put(type, new MapperProxyFactory<T>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); // 解析註解中的信息 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
其實就是獲取當前映射文件的命名空間,並獲取其Class,也就是獲取每一個Mapper接口,而後爲每一個Mapper接口建立一個代理類工廠,new MapperProxyFactory<T>(type),並放進 knownMappers 這個HashMap中,咱們來看看這個MapperProxyFactory
public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return this.methodCache; } protected T newInstance(MapperProxy<T> mapperProxy) { //生成mapperInterface的代理類 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } }
這一塊咱們後面文章再來看是如何調用的。