<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      上文源碼分析Mybatis MapperProxy創(chuàng)建流程
      <https://yq.aliyun.com/articles/718482?spm=a2c4e.11155435.0.0.323233123lqEeh>
      重點(diǎn)闡述MapperProxy的創(chuàng)建流程,但并沒有介紹.Mapper.java(UserMapper.java)是如何與
      Mapper.xml文件中的SQL語句是如何建立關(guān)聯(lián)的。本文將重點(diǎn)接開這個謎團(tuán)。

      接下來重點(diǎn)從源碼的角度分析Mybatis MappedStatement的創(chuàng)建流程。

      @TOC <https://yq.aliyun.com/go/articleRenderRedirect?url=%23>

      1、上節(jié)回顧

      我們注意到這里有兩三個與Mapper相關(guān)的配置:

      * SqlSessionFactory#mapperLocations,指定xml文件的配置路徑。
      * SqlSessionFactory#configLocation,指定mybaits的配置文件,該配置文件也可以配置mapper.xml的配置路徑信息。
      * MapperScannerConfigurer,掃描Mapper的java類(DAO)。
      我們已經(jīng)詳細(xì)介紹了Mybatis Mapper對象的掃描與構(gòu)建,那接下來我們將重點(diǎn)介紹MaperProxy與mapper.xml文件是如何建立關(guān)聯(lián)關(guān)系的。

      根據(jù)上面的羅列以及上文的講述,Mapper.xml與Mapper建立聯(lián)系主要的入口有三:

      1)MapperScannerConfigurer掃描Bean流程中,在調(diào)用MapperReigistry#addMapper時如果Mapper對應(yīng)的映射文件(Mapper.xml)未加載到內(nèi)存,會觸發(fā)加載。
      2)實(shí)例化SqlSessionFactory時,如果配置了mapperLocations。
      3)示例化SqlSessionFactory時,如果配置了configLocation。


      本節(jié)的行文思路:從SqlSessionFacotry的初始化開始講起,因?yàn)閙apperLocations、configLocation都是是SqlSessionFactory的屬性。

      溫馨提示:下面開始從源碼的角度對其進(jìn)行介紹,大家可以先跳到文末看看其調(diào)用序列圖。

      2、SqlSessionFacotry
      if (xmlConfigBuilder != null) { // XMLConfigBuilder // @1 try {
      xmlConfigBuilder.parse(); if (logger.isDebugEnabled()) { logger.debug("Parsed
      configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " +
      this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if
      (!isEmpty(this.mapperLocations)) { // @2 for (Resource mapperLocation :
      this.mapperLocations) { if (mapperLocation == null) { continue; } try {
      XMLMapperBuilder xmlMapperBuilder = new
      XMLMapperBuilder(mapperLocation.getInputStream(), configuration,
      mapperLocation.toString(), configuration.getSqlFragments());
      xmlMapperBuilder.parse(); } catch (Exception e) { throw new
      NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'",
      e); } finally { ErrorContext.instance().reset(); } if (logger.isDebugEnabled())
      { logger.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if
      (logger.isDebugEnabled()) { logger.debug("Property 'mapperLocations' was not
      specified or no matching resources found"); } }
      上文有兩個入口:
      代碼@1:處理configLocation屬性。
      代碼@2:處理mapperLocations屬性。


      我們先從XMLConfigBuilder#parse開始進(jìn)行追蹤。該方法主要是解析configLocation指定的配置路徑,對其進(jìn)行解析,具體調(diào)用parseConfiguration方法。

      2.1 XMLConfigBuilder

      我們直接查看其parseConfiguration方法。
      private void parseConfiguration(XNode root) { try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties
      first typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after
      objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers")); // @1 } catch (Exception e) { throw
      new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
      } }
      重點(diǎn)關(guān)注mapperElement,從名稱與參數(shù)即可以看出,該方法主要是處理中mappers的定義,即mapper
      sql語句的解析與處理。如果使用過Mapper的人應(yīng)該不難知道,我們使用mapper節(jié)點(diǎn),通過resource標(biāo)簽定義具體xml文件的位置。

      2.1.1XMLConfigBuilder#mapperElement
      private void mapperElement(XNode parent) throws Exception { if (parent !=
      null) { for (XNode child : parent.getChildren()) { if
      ("package".equals(child.getName())) { String mapperPackage =
      child.getStringAttribute("name"); configuration.addMappers(mapperPackage); }
      else { String resource = child.getStringAttribute("resource"); String url =
      child.getStringAttribute("url"); String mapperClass =
      child.getStringAttribute("class"); 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()); // @1 mapperParser.parse(); } 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(); } 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."); } } } } }

      上面的代碼比較簡單,不難看出,解析出Mapper標(biāo)簽,解析出resource標(biāo)簽的屬性,創(chuàng)建對應(yīng)的文件流,通過構(gòu)建XMLMapperBuilder來解析對應(yīng)的mapper.xml文件。此時大家會驚訝的發(fā)現(xiàn),在SqlSessionFacotry的初始化代碼中,處理mapperLocations時就是通過構(gòu)建XMLMapperBuilder來解析mapper文件,其實(shí)也不難理解,因?yàn)檫@是mybatis支持的兩個地方可以使用mapper標(biāo)簽來定義mapper映射文件,具體解析代碼當(dāng)然是一樣的邏輯。那我們解析來重點(diǎn)把目光投向XMLMapperBuilder。

      2.2 XMLMapperBuilder
      XMLMapperBuilder#parse public void parse() { if
      (!configuration.isResourceLoaded(resource)) { // @1
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource); bindMapperForNamespace(); }
      parsePendingResultMaps(); // @2 parsePendingChacheRefs(); // @3
      parsePendingStatements(); // @4 }

      代碼@1:如果該映射文件(*.Mapper.xml)文件未加載,則首先先加載,完成xml文件的解析,提取xml中與mybatis相關(guān)的數(shù)據(jù),例如sql、resultMap等等。
      代碼@2:處理mybatis xml中ResultMap。
      代碼@3:處理mybatis緩存相關(guān)的配置。
      代碼@4:處理mybatis statment相關(guān)配置,這里就是本篇關(guān)注的,Sql語句如何與Mapper進(jìn)行關(guān)聯(lián)的核心實(shí)現(xiàn)。

      接下來我們重點(diǎn)探討parsePendingStatements()方法,解析statement(對應(yīng)SQL語句)。

      2.2.1 XMLMapperBuilder#parsePendingStatements
      private void parsePendingStatements() { Collection<XMLStatementBuilder>
      incompleteStatements = configuration.getIncompleteStatements(); synchronized
      (incompleteStatements) { Iterator<XMLStatementBuilder> iter =
      incompleteStatements.iterator(); // @1 while (iter.hasNext()) { try {
      iter.next().parseStatementNode(); // @2 iter.remove(); } catch
      (IncompleteElementException e) { // Statement is still missing a resource... }
      } } }
      代碼@1:遍歷解析出來的所有SQL語句,用的是XMLStatementBuilder對象封裝的,故接下來重點(diǎn)看一下代碼@2,如果解析statmentNode。

      2.2.2 XMLStatementBuilder#parseStatementNode
      public void parseStatementNode() { String id =
      context.getStringAttribute("id"); // @1 start 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); Class<?> resultTypeClass = resolveClass(resultType);
      String resultSetType = context.getStringAttribute("resultSetType");
      StatementType statementType =
      StatementType.valueOf(context.getStringAttribute("statementType",
      StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum =
      resolveResultSetType(resultSetType); String nodeName =
      context.getNode().getNodeName(); 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 Fragments
      before parsing XMLIncludeTransformer includeParser = new
      XMLIncludeTransformer(configuration, builderAssistant);
      includeParser.applyIncludes(context.getNode()); // Parse selectKey after
      includes and remove them. processSelectKeyNodes(id, parameterTypeClass,
      langDriver); // @1 end // Parse the SQL (pre: <selectKey> and <include> were
      parsed and removed) SqlSource sqlSource =
      langDriver.createSqlSource(configuration, context, parameterTypeClass); // @2
      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)) ? new Jdbc3KeyGenerator() : new
      NoKeyGenerator(); } builderAssistant.addMappedStatement(id, sqlSource,
      statementType, sqlCommandType, // @3 fetchSize, timeout, parameterMap,
      parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache,
      useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId,
      langDriver, resultSets); }
      這個方法有點(diǎn)長,其關(guān)注點(diǎn)主要有3個:
      代碼@1:構(gòu)建基本屬性,其實(shí)就是構(gòu)建MappedStatement的屬性,因?yàn)镸appedStatement對象就是用來描述Mapper-SQL映射的對象。

      代碼@2:根據(jù)xml配置的內(nèi)容,解析出實(shí)際的SQL語句,使用SqlSource對象來表示。


      代碼@3:使用MapperBuilderAssistant對象,根據(jù)準(zhǔn)備好的屬性,構(gòu)建MappedStatement對象,最終將其存儲在Configuration中。

      2.2.3 Configuration#addMappedStatement
      public void addMappedStatement(MappedStatement ms) {
      mappedStatements.put(ms.getId(), ms); }
      MappedStatement的id為:mapperInterface +
      methodName,例如com.demo.dao.UserMapper.findUser。


      即上述流程完成了xml的解析與初始化,對終極目標(biāo)是創(chuàng)建MappedStatement對象,上一篇文章介紹了mapperInterface的初始化,最終會初始化為MapperProxy對象,那這兩個對象如何關(guān)聯(lián)起來呢?

      從下文可知,MapperProxy與MappedStatement是在調(diào)用具M(jìn)apper方法時,可以根據(jù)mapperInterface.getName +
      methodName構(gòu)建出MappedStatement的id,然后就可以從Configuration的mappedStatements容器中根據(jù)id獲取到對應(yīng)的MappedStatement對象,這樣就建立起聯(lián)系了。

      其對應(yīng)的代碼:
      // MapperMethod 構(gòu)造器 public MapperMethod(Class<?> mapperInterface, Method
      method, Configuration config) { this.command = new SqlCommand(config,
      mapperInterface, method); this.method = new MethodSignature(config, method); }
      // SqlCommand 構(gòu)造器 public SqlCommand(Configuration configuration, Class<?>
      mapperInterface, Method method) throws BindingException { String statementName
      = mapperInterface.getName() + "." + method.getName(); MappedStatement ms =
      null; if (configuration.hasStatement(statementName)) { ms =
      configuration.getMappedStatement(statementName); } else if
      (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
      String parentStatementName = method.getDeclaringClass().getName() + "." +
      method.getName(); if (configuration.hasStatement(parentStatementName)) { ms =
      configuration.getMappedStatement(parentStatementName); } } if (ms == null) {
      throw new BindingException("Invalid bound statement (not found): " +
      statementName); } name = ms.getId(); type = ms.getSqlCommandType(); if (type ==
      SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method
      for: " + name); } }

      怎么樣,從上面的源碼分析中,大家是否已經(jīng)了解MapperProxy與Xml中的SQL語句是怎樣建立的關(guān)系了嗎?為了讓大家更清晰的了解上述過程,現(xiàn)給出其調(diào)用時序圖:


      原文發(fā)布時間為:2019-05-23
      本文作者:丁威,《RocketMQ技術(shù)內(nèi)幕》作者。
      本文來自中間件興趣圈
      <https://yq.aliyun.com/go/articleRenderRedirect?url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FNMjjH5wf6FxExp6iMXbV_g>
      ,了解相關(guān)信息可以關(guān)注中間件興趣圈
      <https://yq.aliyun.com/go/articleRenderRedirect?url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FNMjjH5wf6FxExp6iMXbV_g>
      。

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          黄色无码电影 | 逼逼操逼 | 国产熟女AAAAA片 | 日韩美女性生活视频 | 外国1级片 | 91成人 在线观看喷潮数学 | 国产无遮挡裸体免费视频 | 亚洲无遮挡| 大香蕉熟女性爱视频 | 中文字幕在线一 |