MyBatis 源碼系列:MyBatis 解析配置文件、二級緩存、SQL

解析全局配置文件

啓動流程分析

String resource = "mybatis-config.xml";
//將XML配置文件構建爲Configuration配置類
reader = Resources.getResourceAsReader(resource);
// 通過加載配置文件流構建一個SqlSessionFactory  DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

通過上面代碼發現,創建SqlSessionFactory的代碼在SqlSessionFactoryBuilder中,進去一探究竟:

//整個過程就是將配置文件解析成Configration對象,然後創建SqlSessionFactory的過程
//Configuration是SqlSessionFactory的一個內部屬性 
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        if (reader != null) {
          reader.close();
        }
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

下面是解析配置文件的核心方法:

  public Configuration parse() {

    //若已經解析過了 就拋出異常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    /**
     * 解析我們的mybatis-config.xml的
     * 節點
     * <configuration>
     * </configuration>
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  /**
   * 解析我們mybatis-config.xml的 configuration節點
   * @param root
   */
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      /**
       * 解析 properties節點
       *     <properties resource="db.properties" />
       *     解析到org.apache.ibatis.parsing.XPathParser#variables
       *          org.apache.ibatis.session.Configuration#variables
       */
      propertiesElement(root.evalNode("properties"));
      /**
       * 解析我們的mybatis-config.xml中的settings節點
       * 具體可以配置哪些屬性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
       * <settings>
       <setting name="cacheEnabled" value="true"/>
       <setting name="lazyLoadingEnabled" value="true"/>
       <setting name="mapUnderscoreToCamelCase" value="false"/>
       <setting name="localCacheScope" value="SESSION"/>
       <setting name="jdbcTypeForNull" value="OTHER"/>
       ..............
       </settings>
       *
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /**
       * 基本沒有用過該屬性
       * VFS含義是虛擬文件系統;主要是通過程序能夠方便讀取本地文件系統、FTP文件系統等系統中的文件資源。
       Mybatis中提供了VFS這個配置,主要是通過該配置可以加載自定義的虛擬文件系統應用程序
       解析到:org.apache.ibatis.session.Configuration#vfsImpl
       */
      loadCustomVfsImpl(settings);
      /**
       * 指定 MyBatis 所用日誌的具體實現,未指定時將自動查找。
       * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
       * 解析到org.apache.ibatis.session.Configuration#logImpl
       */
      loadCustomLogImpl(settings);
      /**
       * 解析我們的別名
       * <typeAliases>
       <typeAlias alias="User" type="com.mcode.entity.User"/>
       </typeAliases>
       <typeAliases>
       <package name="com.mcode.entity"/>
       </typeAliases>
       解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
       除了自定義的,還有內置的
       */
      typeAliasesElement(root.evalNode("typeAliases"));
      /**
       * 解析我們的插件(比如分頁插件)
       * mybatis自帶的
       * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
       ParameterHandler (getParameterObject, setParameters)
       ResultSetHandler (handleResultSets, handleOutputParameters)
       StatementHandler (prepare, parameterize, batch, update, query)
       解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
       */
      pluginsElement(root.evalNode("plugins"));
      /**
       * 可以配置  一般不會去設置
       * 對象工廠 用於反射實例化對象、對象包裝工廠、
       * 反射工廠 用於屬性和setter/getter 獲取
       */
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));

      // 設置settings 和默認值到configuration
      settingsElement(settings);

      /**
       * 解析我們的mybatis環境
       <environments default="dev">
       <environment id="dev">
       <transactionManager type="JDBC"/>
       <dataSource type="POOLED">
       <property name="driver" value="${jdbc.driver}"/>
       <property name="url" value="${jdbc.url}"/>
       <property name="username" value="root"/>
       <property name="password" value="Zw726515"/>
       </dataSource>
       </environment>
       </environments>
       *  解析到:org.apache.ibatis.session.Configuration#environment
       *  在集成spring情況下由 spring-mybatis提供數據源 和事務工廠
       */
      environmentsElement(root.evalNode("environments"));
      /**
       * 解析數據庫廠商
       *     <databaseIdProvider type="DB_VENDOR">
       <property name="SQL Server" value="sqlserver"/>
       <property name="DB2" value="db2"/>
       <property name="Oracle" value="oracle" />
       <property name="MySql" value="mysql" />
       </databaseIdProvider>
       *  解析到:org.apache.ibatis.session.Configuration#databaseId
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /**
       * 解析我們的類型處理器節點
       * <typeHandlers>
       <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
       </typeHandlers>
       解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
       */
      typeHandlersElement(root.evalNode("typeHandlers"));
      /**
       * 解析我們的mapper
       *
       resource:來註冊我們的class類路徑下的
       url:來指定我們磁盤下的或者網絡資源的
       class:
       若註冊Mapper不帶xml文件的,這裏可以直接註冊
       若註冊的Mapper帶xml文件的,需要把xml文件和mapper文件同名 同路徑
       -->
       <mappers>
       <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
       <mapper class="com.mcode.mapper.EmployeeMapper"></mapper>


       <package name="com.mcode.mapper"></package>
       -->
       </mappers>
       * package
       *     ·解析mapper接口代理工廠(傳入需要代理的接口) 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
       ·解析mapper.xml  最終解析成MappedStatement 到:org.apache.ibatis.session.Configuration#mappedStatements
       */
      mappersElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

image

上面解析流程結束後會生成一個Configration對象,包含所有配置信息,然後會創建一個SqlSessionFactory對象,這個對象包含了Configration對象。

簡單總結

對於MyBatis啓動的流程(獲取SqlSession的過程)這邊簡單總結下:

  • SqlSessionFactoryBuilder解析配置文件,包括屬性配置、別名配置、攔截器配置、環境(數據源和事務管理器)、Mapper配置等;解析完這些配置後會生成一個Configration對象,這個對象中包含了MyBatis需要的所有配置,然後會用這個Configration對象創建一個SqlSessionFactory對象,這個對象中包含了Configration對象;

image

二級緩存解析

解析二級緩存

解析mapper文件

  private void mappersElement(XNode context) throws Exception {
    if (context == null) {
      return;
    }
    /**
     * 獲取我們mappers節點下的一個一個的mapper節點
     */
    for (XNode child : context.getChildren()) {
      /**
       * 判斷我們mapper是不是通過批量註冊的
       * <package name="com.mcode.mapper"></package>
       */
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        /**
         * 判斷從classpath下讀取我們的mapper
         * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
         */
        String resource = child.getStringAttribute("resource");
        /**
         * 判斷是不是從我們的網絡資源讀取(或者本地磁盤得)
         * <mapper url="D:/mapper/EmployeeMapper.xml"/>
         */
        String url = child.getStringAttribute("url");
        /**
         * 解析這種類型(要求接口和xml在同一個包下)
         * <mapper class="com.mcode.mapper.DeptMapper"></mapper>
         *
         */
        String mapperClass = child.getStringAttribute("class");
        /**
         * 我們得mappers節點只配置了
         * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
         */
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          /**
           * 把我們的文件讀取出一個流
           */
          try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
            /**
             * 創建讀取XmlMapper構建器對象,用於來解析我們的mapper.xml文件
             */
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                configuration.getSqlFragments());
            /**
             * 真正的解析我們的mapper.xml配置文件(說白了就是來解析我們的sql)
             */
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {        
          ErrorContext.instance().resource(url);
          try (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

  /**
   * 真正的去解析我們的Mapper.xml(EmployeeMapper.xml)
   */
  public void parse() {
    /**
     * 判斷當前的Mapper是否被加載過
     */
    if (!configuration.isResourceLoaded(resource)) {
      /**
       * 真正的解析我們的 <mapper namespace="com.mcode.mapper.EmployeeMapper">
       *
       */
      configurationElement(parser.evalNode("/mapper"));
      /**
       * 把資源保存到我們Configuration中
       */
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  /**
   * 解析我們的<mapper></mapper>節點
   * @param context
   */
  private void configurationElement(XNode context) {
    try {
      /**
       * 解析我們的namespace屬性
       * <mapper namespace="com.cmode.mapper.EmployeeMapper">
       */
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      /**
       * 保存我們當前的namespace  並且判斷接口完全類名==namespace
       */
      builderAssistant.setCurrentNamespace(namespace);
      /**
       * 解析我們的緩存引用
       * 說明我當前的緩存引用和DeptMapper的緩存引用一致
       * <cache-ref namespace="com.tuling.mapper.DeptMapper"></cache-ref>
       解析到org.apache.ibatis.session.Configuration#cacheRefMap<當前namespace,ref-namespace>
       異常下(引用緩存未使用緩存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
       */
      cacheRefElement(context.evalNode("cache-ref"));
      /**
       * 解析我們的cache節點
       * <cache ></cache>
       解析到:org.apache.ibatis.session.Configuration#caches
       org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
       */
      cacheElement(context.evalNode("cache"));
      /**
       * 解析paramterMap節點(該節點mybaits3.5貌似不推薦使用了)
       */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      /**
       * 解析我們的resultMap節點
       * 解析到:org.apache.ibatis.session.Configuration#resultMaps
       *    異常 org.apache.ibatis.session.Configuration#incompleteResultMaps
       *
       */
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      /**
       * 解析我們通過sql片段
       *  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
       *   其實等於 org.apache.ibatis.session.Configuration#sqlFragments
       *   因爲他們是同一引用,在構建XMLMapperBuilder 時把Configuration.getSqlFragments傳進去了
       */
      sqlElement(context.evalNodes("/mapper/sql"));
      /**
       * 解析我們的select | insert |update |delete節點
       * 解析到org.apache.ibatis.session.Configuration#mappedStatements
       */
      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);
    }
  }

解析Cache

  /**
   * 解析cache
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      //解析cache節點的type屬性
      String type = context.getStringAttribute("type", "PERPETUAL");
      // 根據別名(或完整限定名)  加載爲Class
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        /*獲取緩存過期策略:默認是LRU
      LRU – 最近最少使用:移除最長時間不被使用的對象。(默認)
      FIFO – 先進先出:按對象進入緩存的順序來移除它們。
      SOFT – 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。
      WEAK – 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除對象。
      */
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //flushInterval(刷新間隔)屬性可以被設置爲任意的正整數,設置的值應該是一個以毫秒爲單位的合理時間量。 默認情況是不設置,也就是沒有刷新間隔,緩存僅僅會在調用語句時刷新。
      Long flushInterval = context.getLongAttribute("flushInterval");
      //size(引用數目)屬性可以被設置爲任意正整數,要注意欲緩存對象的大小和運行環境中可用的內存資源。默認值是 1024。
      Integer size = context.getIntAttribute("size");
      //只讀)屬性可以被設置爲 true 或 false。只讀的緩存會給所有調用者返回緩存對象的相同實例。 因此這些對象不能被修改。這就提供了可觀的性能提升。而可讀寫的緩存會(通過序列化)返回緩存對象的拷貝。 速度上會慢一些,但是更安全,因此默認值是 false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //把緩存節點加入到Configuration中
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

image

緩存中的調用過程

image

緩存中使用的設計模式

image

解析SQL

buildStatementFromContext

/**
   * 方法實現說明:解析我們得得select|update|delte|insert節點然後
   * 創建我們得mapperStatment對象
   * @param list:所有的select|update|delte|insert節點
   * @param requiredDatabaseId:判斷有沒有數據庫廠商Id
   */
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    /**
     * 循環我們的select|delte|insert|update節點
     */
    for (XNode context : list) {
      /**
       * 創建一個xmlStatement的構建器對象
       */
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

parseStatementNode

 public void parseStatementNode() {
    /**
     * 我們的insert|delte|update|select 語句的sqlId
     */
    String id = context.getStringAttribute("id");
    /**
     * 判斷我們的insert|delte|update|select  節點是否配置了
     * 數據庫廠商標註
     */
    String databaseId = context.getStringAttribute("databaseId");

    /**
     * 匹配當前的數據庫廠商id是否匹配當前數據源的廠商id
     */
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    /**
     * 獲得節點名稱:select|insert|update|delete
     */
    String nodeName = context.getNode().getNodeName();
    /**
     * 根據nodeName 獲得 SqlCommandType枚舉
     */
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    /**
     * 判斷是不是select語句節點
     */
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    /**
     *  獲取flushCache屬性
     *  默認值爲isSelect的反值:查詢:flushCache=false   增刪改:flushCache=true
     */
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    /**
     * 獲取useCache屬性
     * 默認值爲isSelect:查詢:useCache=true   增刪改:useCache=false
     */
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);

    /**
     * resultOrdered:  是否需要分組:
     *  select * from user-->User{id=1, name='User1', groups=[1, 2], roles=[1, 2, 3]}
     */
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    /**
     * 解析我們的sql公用片段
     *     <select id="qryEmployeeById" resultType="Employee" parameterType="int">
              <include refid="selectInfo"></include>
              employee where id=#{id}
          </select>
        將 <include refid="selectInfo"></include> 解析成sql語句 放在<select>Node的子節點中
     */
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    /**
     * 解析我們sql節點的參數類型
     */
    String parameterType = context.getStringAttribute("parameterType");
    // 把參數類型字符串轉化爲class
    Class<?> parameterTypeClass = resolveClass(parameterType);

    /**
     * 查看sql是否支撐自定義語言
     * <delete id="delEmployeeById" parameterType="int" lang="mcodeLang">
     <settings>
          <setting name="defaultScriptingLanguage" value="mcodeLang"/>
     </settings>
     */
    String lang = context.getStringAttribute("lang");
    /**
     * 獲取自定義sql腳本語言驅動 默認:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
     */
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    /**
     * 解析我們<insert 語句的的selectKey節點, 一般在oracle裏面設置自增id
     */
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    /**
     * 我們insert語句 用於主鍵生成組件
     */
    KeyGenerator keyGenerator;
    /**
     * selectById!selectKey
     * id+!selectKey
     */
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    /**
     * 把我們的命名空間拼接到keyStatementId中
     * com.mcode.mapper.Employee.saveEmployee!selectKey
     */
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    /**
     *<insert id="saveEmployee" parameterType="com.mcode.entity.Employee" useGeneratedKeys="true" keyProperty="id">
     *判斷我們全局的配置類configuration中是否包含以及解析過的主鍵生成器對象
     */
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {

      /**
       * 若我們<insert 配置了useGeneratedKeys 那麼就取useGeneratedKeys的配置值,
       * 否者就看我們的mybatis-config.xml配置文件中是配置了
       * <setting name="useGeneratedKeys" value="true"></setting> 默認是false
       * 並且判斷sql操作類型是否爲insert
       * 若是的話,那麼使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
       * 否則就是NoKeyGenerator.INSTANCE
       */
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    /**
     * 通過class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver來解析我們的
     * sql腳本對象  .  解析SqlNode. 注意, 只是解析成一個個的SqlNode, 並不會完全解析sql,因爲這個時候參數都沒確定,動態sql無法解析
     */
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    /**
     * STATEMENT,PREPARED 或 CALLABLE 中的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED
     */
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    /**
     * 這是一個給驅動的提示,嘗試讓驅動程序每次批量返回的結果行數和這個設置值相等。 默認值爲未設置(unset)(依賴驅動)
     */
    Integer fetchSize = context.getIntAttribute("fetchSize");
    /**
     * 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值爲未設置(unset)(依賴驅動)。
     */
    Integer timeout = context.getIntAttribute("timeout");
    /**
     * 將會傳入這條語句的參數類的完全限定名或別名。這個屬性是可選的,因爲 MyBatis 可以通過類型處理器(TypeHandler) 推斷出具體傳入語句的參數,默認值爲未設置
     */
    String parameterMap = context.getStringAttribute("parameterMap");
    /**
     * 從這條語句中返回的期望類型的類的完全限定名或別名。 注意如果返回的是集合,那應該設置爲集合包含的類型,而不是集合本身。
     * 可以使用 resultType 或 resultMap,但不能同時使用
     */
    String resultType = context.getStringAttribute("resultType");
    /**解析我們查詢結果集返回的類型     */
    Class<?> resultTypeClass = resolveClass(resultType);
    /**
     * 外部 resultMap 的命名引用。結果集的映射是 MyBatis 最強大的特性,如果你對其理解透徹,許多複雜映射的情形都能迎刃而解。
     * 可以使用 resultMap 或 resultType,但不能同時使用。
     */
    String resultMap = context.getStringAttribute("resultMap");

    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }

    /**
     * 解析 keyProperty  keyColumn 僅適用於 insert 和 update
     */
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    /**
     * 爲我們的insert|delete|update|select節點構建成我們的mappedStatment對象
     */
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

createSqlSource

  /**
   * 方法實現說明:創建我們的sqlSource對象
   * @param configuration:全局配置
   * @param script:腳本類型
   * @param parameterType:參數類型
   */
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

parseScriptNode

 public SqlSource parseScriptNode() {
    /**
     * 遞歸解析-組合設計模式  selectById這個sql元素會解析成
     *    1層  MixedSqlNode <SELECT>
     *    2層  WhereSqlNode <WHERE>
     *    3層  IfSqlNode <IF>
     *       test="條件表達式"
     *
     *  contexts= sql語句分: 1.TextSqlNode 帶${}   2.StaticTextSqlNode
     */
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      // 動態Sql源
      // 動態Sql 就是還需要後續執行時根據傳入參數動態解析Sql(因爲有<if>等,還要拼接${}sql)
      //    和參數ParameterMappings   也會在後續執行解析,因爲動態條件肯定會有動態參數
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 靜態Sql源  如果沒有動態標籤(<if>、<where>等) 以及 沒有${}  就是靜態Sql源
      // 靜態Sql 就是在這裏就解析了Sql  和參數ParameterMappings   後續執行就不用解析了
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    // 其實他們的區別就是動態sql 需要在查詢的時候解析 因爲有動態sql 和拼接${}
    //                  靜態sql 已經在這裏確定好sql. 和參數ParameterMapping,
    return sqlSource;
  }

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章