在上一篇文章Mybatis源碼解析,一步一步從淺入深(四):將configuration.xml的解析到Configuration對象實例中咱們談到了properties,settings,environments節點的解析,總結一下,針對示例工程的configuration.xml文件來講properties節點的解析就是將dbConfig.properties中的數據庫配置信息加載到了configuration實例的variables中,settings節點的解析讓configuration使用了咱們配置的log4j日誌系統,environments節點的解析生成了數據庫環境類(Environment)的實例對象,並將這個示例對象賦值給了 configuration的environment屬性。那麼接下來咱們着重看一下mappers節點的解析。mappers節點的解析很是重要,因此本文篇幅會很長。html
一,先看看示例工程的mappers節點和userDao-mapping.xml文件node
mappers節點:sql
<!-- 映射文件,mybatis精髓 --> <mappers> <mapper resource="mapper/userDao-mapping.xml"/> </mappers>
userDao-mapping.xml文件:數據庫
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <mapper namespace="com.zcz.learnmybatis.dao.UserDao"> <select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > select * from user where id = #{id} </select> </mapper>
userDao-mapping.xml文件很簡單,定義了一個namespace屬性指向UserDao接口,定義了一個select標籤聲明。apache
二,看一下解析mappers節點的方法mapperElement的源碼:網絡
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { //檢測是不是package節點 String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //讀取<mapper resource="mapper/userDao-mapping.xml"/>中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml" String resource = child.getStringAttribute("resource"); //讀取mapper節點的url屬性 String url = child.getStringAttribute("url"); //讀取mapper節點的class屬性 String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { //根據rusource加載mapper文件 ErrorContext.instance().resource(resource); //讀取文件字節流 InputStream inputStream = Resources.getResourceAsStream(resource); //實例化mapper解析器 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //執行解析mapper文件,即解析mapper/userDao-mapping.xml,文件 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { //從網絡url資源加載mapper文件 ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { //使用mapperClass加載文件 Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { //resource,url,mapperClass三種配置方法只能使用其中的一種,不然就報錯 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
一目瞭然,遍歷mappers中的mapper節點,而後逐一解析。咱們的配置文件中只有一個mapper節點,因此這裏要解析的就是mapper/userDao-mapping.xml文件。mybatis
解析mapper/userDao-mapping.xml文件的關鍵代碼是app
//實例化mapper解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//執行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
mapperParser.parse();post
接下來逐句進行分析fetch
三,實例化mapper解析器:XMLMapperBuilder
代碼:XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
查看mapper解析器XMLMapperBuilder類的聲明能夠發現,mapper解析器類XMLMapperBuilder和xml配置解析器XMLConfigBuilder同時繼承了父類BaseBuilder。其實後面還有幾個類也繼承了父類BaseBuilder。
public class XMLMapperBuilder extends BaseBuilder {}
看一下使用到的XMLMapperBuilder構造方法
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }
這裏也建立了一個XPathParser xml文件解析器,緣由很簡單,由於mapper/userDao-mapping.xml也是一個xml文件(手動捂臉)。至於最後一個參數 Map<String, XNode> sqlFragments 是什麼?如今只知道sqlFragments = configuration.getSqlFragments(),可是具體是什麼呢?稍後纖細介紹。
接下來使用this關鍵字,調用了XMLMapperBuilder的私有構造方法:
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; }
看過Mybatis源碼解析,一步一步從淺入深(三):實例化xml配置解析器(XMLConfigBuilder)的同窗想必已經知道supper()方法作了什麼,這裏就再也不贅述了。值得一提的是MapperBuilderAssistant(mapper解析器助手),既然是助手,那確定就是輔助mapper解析器解析mapper文件的了。
public class MapperBuilderAssistant extends BaseBuilder {}
看看MapperBuilderAssistant類的聲明,它也是繼承了BaseBuilder的。
到這裏mapper解析器(XMLMapperBuilder)的實例化工做就已經完成了。可是爲了更好了進行接下來的分析,咱們有必要再認識Configuration類的一些屬性和方法:
public class Configuration { protected Environment environment; protected Properties variables = new Properties(); ...... //初始化值爲null protected String databaseId; protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); // 這是一個HashMap ,存放的是已經解析過的sql聲明,String 類型的鍵,例如com.zcz.learnmybatis.entity.User.findUserById,值是MappedStatement實例對象 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection"); ...... //這是一個無序不重複的Set集合,裏面存放的是已經加載解析過的 mapper文件名。例如<mapper resource="mapper/userDao-mapping.xml"/>中的mapper/userDao-mapping.xml protected final Set<String> loadedResources = new HashSet<String>(); ...... //sql碎片Map,鍵String 值XNode,這個Map中存放的是已經在先前的mapper中解析過的碎片 protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers"); ...... public Configuration() { ..... // 註冊默認的XML語言驅動 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); } ...... //將resource 添加到 加載解析完成Set loadedResources中 public void addLoadedResource(String resource) { loadedResources.add(resource); } //檢測mapper文件是否已經被加載解析過,resource是<mapper resource="mapper/userDao-mapping.xml"/>中的resource public boolean isResourceLoaded(String resource) { return loadedResources.contains(resource); } ...... //根據標籤聲明類(MappedStatement) 實例對象的id獲取 解析過的標籤聲明類實例對象
// 標籤聲明是什麼,在下文中會給出解釋 public MappedStatement getMappedStatement(String id) { return this.getMappedStatement(id, true); } //根據標籤聲明類(MappedStatement) 實例對象的id獲取 解析過的標籤聲明類實例對象 public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); } //獲取sql碎片 public Map<String, XNode> getSqlFragments() { return sqlFragments; } ...... // 根據檢查是否存在 標籤聲明名稱 爲statementName 的標籤聲明 public boolean hasStatement(String statementName) { return hasStatement(statementName, true); } // 根據檢查是否存在 標籤聲明名稱 爲statementName 的標籤聲明 public boolean hasStatement(String statementName, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.containsKey(statementName); } ...... }
四,執行解析mapper文件,即解析mapper/userDao-mapping.xml文件
代碼:mapperParser.parse();
看一下負責解析mapper文件的parser方法的源代碼:
public void parse() { // 先判斷mapper文件是否已經解析 if (!configuration.isResourceLoaded(resource)) { //執行解析 configurationElement(parser.evalNode("/mapper")); //保存解析記錄 configuration.addLoadedResource(resource); // 綁定已經解析的命名空間 bindMapperForNamespace(); } ...... }
很明顯,真正解析mapper文件的代碼是configurationElement方法:
private void configurationElement(XNode context) { try { //獲取mapper文件中的mapper節點的namespace屬性 com.zcz.learnmybatis.entity.UserDao String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } //將namespace賦值給映射 mapper解析器助理builderAssistant.currentNameSpace,即告訴mapper解析器助理如今解析的是那個mapper文件 builderAssistant.setCurrentNamespace(namespace); //解析cache-ref節點 cacheRefElement(context.evalNode("cache-ref")); //解析cache節點 cacheElement(context.evalNode("cache")); //解析parameterMap節點,這裏爲何要使用"/mapper/parameterMap"而不是直接使用"parameterMap",由於parameterMap能夠配置多個,並且使用的是context.evalNodes方法,注意不是evalNode了,是evalNodes。 parameterMapElement(context.evalNodes("/mapper/parameterMap")); //解析resultMap節點 resultMapElements(context.evalNodes("/mapper/resultMap")); //解析sql節點 sqlElement(context.evalNodes("/mapper/sql")); //解析select|insert|update|delete節點,注意context.evalNodes()方法,返回的是一個List集合。 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
從上面代碼看到,處理完namespace以後,就是解析mapper文件中的節點了,可是咱們的userDao-mapping.xml文件中只有一個select標籤:
<select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > select * from user where id = #{id} </select>
那麼只須要分析最有一個方法buildStatementFromContext就能夠了。
看源碼:
這個方法中的惟一的參數list 就是userDao-mapping.xml中的select,update,delete,insert標籤們。注意是一個List。也就是說可能會有多個。
private void buildStatementFromContext(List<XNode> list) {
//這裏的confuration.getDatabaseId 是 null ,由於 configuration初始化時沒有給默認值,在虛擬機實例化configuration對象時,賦予默認值null if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }
又調用了buildStatementFromContext 重載方法:
在這個方法中遍歷了上面咱們提到的list.而咱們的userDao-mapping.xml中的select標籤就是在這個list中。
咱們都知道,在mapper文件中,select標籤,update標籤,delete標籤,insert標籤的id屬性對應着namespace中的接口的方法名。因此咱們就稱一個select標籤,update標籤,delete標籤或者一個insert標籤爲一個標籤聲明。
那麼這個方法中就是遍歷了全部的標籤聲明,並逐一解析。
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) {
//初始化標籤聲明解析器(XMLStatementBuilder) final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try {
// 執行標籤聲明的解析 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
到如今咱們終於明白,原來buildStatementFromContext 也是不負責解析的,真正負責解析的是final 修飾的 XMLStatementBuilder類 的實例對象 statementParser。
public class XMLStatementBuilder extends BaseBuilder {}
發現了什麼?
XMLStatementBuilder也是繼承BaseBuilder的。
看看構造方法:
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) { super(configuration); this.builderAssistant = builderAssistant; this.context = context; this.requiredDatabaseId = databaseId; }
只有一些賦值操做。
重點就是try-catch塊中的執行標籤聲明的解析的:statementParser.parseStatementNode();這一句代碼了。
看源碼:
1 public void parseStatementNode() { 2 // 獲取的是select 標籤的id屬性,即id="findUserById" 3 String id = context.getStringAttribute("id"); 4 // 沒有databaseId屬性,即databaseId = null; 5 String databaseId = context.getStringAttribute("databaseId"); 6 // 判斷databaseId,這一行代碼下方有詳細介紹 7 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return; 8 // 沒有fetchSize屬性,即fetchSize = null; 9 Integer fetchSize = context.getIntAttribute("fetchSize"); 10 // 沒有timeout屬性,即timeout = null; 11 Integer timeout = context.getIntAttribute("timeout"); 12 // 沒有 parameterMap 屬性,即 parameterMap = null; 13 String parameterMap = context.getStringAttribute("parameterMap"); 14 // 沒有 parameterType 屬性,即 parameterType = null; 15 String parameterType = context.getStringAttribute("parameterType"); 16 // parameterType = null,即parameterTypeClass = null 17 Class<?> parameterTypeClass = resolveClass(parameterType); 18 // 沒有 resultMap 屬性,即 resultMap = null; 19 String resultMap = context.getStringAttribute("resultMap"); 20 // 獲取的是select 標籤的resultType屬性,即resultType="com.zcz.learnmybatis.entity.User" 21 String resultType = context.getStringAttribute("resultType"); 22 // 沒有 lang 屬性,即 lang = null; 23 String lang = context.getStringAttribute("lang"); 24 //獲取默認的語言驅動 XMLLanguageDriver 25 LanguageDriver langDriver = getLanguageDriver(lang); 26 27 // 獲取User類的類對象 28 Class<?> resultTypeClass = resolveClass(resultType); 29 // 沒有 resultSetType 屬性,即 resultSetType = null; 30 String resultSetType = context.getStringAttribute("resultSetType"); 31 // 沒有 statementType 屬性,返回默認的 「PREPARED」 即statementType = PREPARED 32 StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); 33 // 沒有 resultSetType 屬性,即 resultSetType = null; 34 ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 35 36 // nodeName = "select" 37 String nodeName = context.getNode().getNodeName(); 38 // sql類型是 sqlCommandType = SELECT 39 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); 40 // isSelect = true; 41 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 42 // 沒有 flushCache 屬性,即 flushCache = null; 取默認flushCache = !isSelect = false; 43 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); 44 // 沒有 useCache 屬性,即 useCache = null; 取默認useCache = isSelect= true; 45 boolean useCache = context.getBooleanAttribute("useCache", isSelect); 46 // 沒有 resultOrdered 屬性,即 resultOrdered = null; 取默認resultOrdered= false; 47 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); 48 49 // Include Fragments before parsing 50 // 處理include 標籤,咱們的select標籤中沒有用到include標籤 51 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); 52 includeParser.applyIncludes(context.getNode()); 53 54 // Parse selectKey after includes and remove them. 55 // 處理selectKey 標籤,咱們的select標籤中沒有用到selectKey標籤 56 processSelectKeyNodes(id, parameterTypeClass, langDriver); 57 58 // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 59 // 在解析完<selectKey> 和 <include> 以後開始解析 SQL語句 60 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 61 // 沒有 resultSets 屬性,即 resultSets = null; 62 String resultSets = context.getStringAttribute("resultSets"); 63 // 沒有 keyProperty 屬性,即 keyProperty = null; 64 String keyProperty = context.getStringAttribute("keyProperty"); 65 // 沒有 keyColumn 屬性,即 keyColumn = null; 66 String keyColumn = context.getStringAttribute("keyColumn"); 67 68 //接下來處理的是主鍵生成器 69 KeyGenerator keyGenerator; 70 String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; 71 keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); 72 if (configuration.hasKeyGenerator(keyStatementId)) { 73 keyGenerator = configuration.getKeyGenerator(keyStatementId); 74 } else { 75 // 應爲咱們的是select類型的語句,因此SqlCommandType.INSERT.equals(sqlCommandType) == false。因此keyGenerator = new NoKeyGenerator() 76 keyGenerator = context.getBooleanAttribute("useGeneratedKeys", 77 configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) 78 ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 79 } 80 81 // 這一步 就是讓mapper解析器助理建立MappedStatement實例對象,並將新建的實例對象添加到 configuration的 mappedStatements中,表示這個標籤聲明被解析過了。 82 //根據上方的解析過程,咱們能夠清晰的知道各個參數的值: 83 //id = "findUserById",sqlSource = RawSqlSource 實例對象,statementType = PREPARED,sqlCommandType = SELECT 84 //fetchSize,timeout,parameterMap,parameterTypeClass,resultMap= null 85 //resultTypeClass = User類對象 86 //resultSetTypeEnum = null 87 //flushCache=false,useCache=true,resultOrdered=false,keyGenerator = new NoKeyGenerator() 88 //keyProperty, keyColumn, databaseId,=null 89 //langDriver=XMLLanguageDriver實例對象 90 //resultSets=null。 91 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 92 fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, 93 resultSetTypeEnum, flushCache, useCache, resultOrdered, 94 keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); 95 }
經過代碼中的註釋,相信你們都能看的明白,這裏着重解釋一下,第7行,第60行,第91行:
第7行:if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;中的databaseIdMatchesCurrent方法源碼:
1 //比較須要使用的databaseId 和 標籤聲明中的databaseId 是否相同,這時id="findUserById",同時databaseId和requiredDatabaseId 都是null 2 private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) { 3 if (requiredDatabaseId != null) { 4 if (!requiredDatabaseId.equals(databaseId)) { 5 //若是不一樣就返回false,中止解析 6 return false; 7 } 8 } else { 9 if (databaseId != null) { 10 // 這個時候requiredDatabaseId == null, 在這個requiredDatabaseId 等於 null的狀況下databaseId 卻不等於null,說明須要使用的databaseId 和 標籤聲明中的databaseId 是不相同的,就放回false,中止解析 11 return false; 12 } 13 // skip this statement if there is a previous one with a not null databaseId 若是存在已經解析過的而且databaseId不爲null的標籤聲明,則返回false跳過解析 14 // 獲取id,這個id就是標籤解析器的id,從接下來的分析中能夠明確看出:這個id也是標籤聲明類(MappedStatement)實例化對象的id。 15 id = builderAssistant.applyCurrentNamespace(id, false); 16 17 if (this.configuration.hasStatement(id, false)) { 18 MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2 19 if (previous.getDatabaseId() != null) { 20 return false; 21 } 22 } 23 } 24 return true; 25 }
而源碼中的applyCurrentNamespace源碼是:
public String applyCurrentNamespace(String base, boolean isReference) { if (base == null) return null; if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) return base; } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) return base; if (base.contains(".")) throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } // 返回com.zcz.learnmybatis.entity.UserDao.findUserById // currentNamespace 在前面已經設置過了,就是mapper文件中的namespace return currentNamespace + "." + base; }
第60行是用來處理SQL語句的,也就是用來處理:
select * from user where id = #{id}
這一部分的,處理了什麼呢?簡單來講就是根據」${「是否存在來判斷SQL語句是不是動態SQL語句,而且把 select * from user where id = #{id} 轉換爲select * from user where id = ?。同時把#{id}中的id保存起來。具體細節源碼不展開了,須要的話,再寫一篇文章詳細解析吧。
第91行,就是把一個select ,update,delete 或者insert標籤聲明轉換爲MappedStatement對象實例,更明白點說,就是把
<select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > select * from user where id = #{id} </select>
這一部分轉換爲MappedStatement對象並保持到configuration中,實現保存的代碼源碼以下:
可是要注意下面代碼裏的id = "findUserById",可是通過applyCurrentNameSpace()方法後,id= "com.zcz.learnmybatis.dao.UserDao.findUserById",即在原來的id前,添加了UserDao類全包名+類名+「.」;
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"); id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //初始化MappenStatement.Builder MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType); statementBuilder.resource(resource); statementBuilder.fetchSize(fetchSize); statementBuilder.statementType(statementType); statementBuilder.keyGenerator(keyGenerator); statementBuilder.keyProperty(keyProperty); statementBuilder.keyColumn(keyColumn); statementBuilder.databaseId(databaseId); statementBuilder.lang(lang); statementBuilder.resultOrdered(resultOrdered); statementBuilder.resulSets(resultSets); setStatementTimeout(timeout, statementBuilder); setStatementParameterMap(parameterMap, parameterType, statementBuilder); setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder); setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder); // 構造MappedStatement MappedStatement statement = statementBuilder.build();
// 保存 configuration.addMappedStatement(statement); return statement; }
值得一提的是在MappedStatement statement = statementBuilder.build();咱們先看看源碼:
1 public MappedStatement build() { 2 assert mappedStatement.configuration != null; 3 assert mappedStatement.id != null; 4 assert mappedStatement.sqlSource != null; 5 assert mappedStatement.lang != null; 6 mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps); 7 return mappedStatement; 8 }
在第六行有一個Collections.unmodifiableList方法,這個方法是一個頗有趣的方法,想要了解一下的話,請查閱:Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet做用及源碼解析
到這裏 select的標籤聲明的解析就結束了,同時Mapper文件的解析也結束了。
總結一下,mappers節點解析完成以後,全部的mybatis有關的配置文件都已經解析完成了,包括:configuration.xml文件,dbConfig.properties文件,userDa0-mapping.xml文件。而且都保存到Configuration 的實例化對象中了。
原創不易,轉載請聲明出處:http://www.javashuo.com/article/p-yxzevcoj-n.html