天天看點

Spring源碼系列09——Mybatis源碼解析Mybatis對比JDBC的優勢:Mybatis源碼解析過程——解析mybtatis-config.xml全局配置檔案Mybatis源碼解析過程——解析mapper.xml配置檔案根據解析後的configuration對象生成sqlsessionfactory對象

Mybatis對比JDBC的優勢:

(1)使用連接配接池管理資料庫連接配接資料庫連接配接,避免了JDBC頻繁的連接配接和斷開資料庫資源;

(2)使用mapper.xml配置檔案的形式,将sql語句與java代碼分離;

(3)mybatis通過parameterType向sql中傳遞參數,更加靈活;

(4)mybatis可以将查詢結果自動映射為java對象,避免了jdbc手工寫代碼的繁瑣過程。

Mybatis源碼解析過程——解析mybtatis-config.xml全局配置檔案

解析mybatis-config.xml配置檔案

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
           

解析properties節點

解析setting節點

解析我們的mybatis環境,也就是資料庫連接配接配置;

解析到:org.apache.ibatis.session.Configuration#environment

* 在內建spring情況下由 spring-mybatis提供資料源 和事務工廠

*

解析我們的類型處理器節點

解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap

解析解析我們的mapper

Mybatis源碼解析過程——解析mapper.xml配置檔案

(1)mybatis中mapper的四種配置方式

(1)package:批量解析mapper配置檔案

(2)resource:按照配置解析mapper檔案

(3)url:從網絡資源或者本地磁盤解析mapper檔案

(4)class:要求接口和xml在同一個包下面

/**
         * 判斷我們mapper是不是通過批量注冊的
         * <package name="com.tuling.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.tuling.mapper.DeptMapper"></mapper>
           *
           */
          String mapperClass = child.getStringAttribute("class");
           

(2)解析package配置的mapper.xml

org.apache.ibatis.binding.MapperRegistry#addMapper

// 根據mapper接口名擷取 xml檔案并解析,  解析<mapper></mapper>裡面所有東西放到configuration
      loadXmlResource();
      // 添加已解析的标記
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
           

根據mapper接口的名稱,轉換處理為xml檔案的實體路徑,然後利用XMLMapperBuilder類進行解析。

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

private void configurationElement(XNode context) {
    try {
      /**
       * 解析我們的namespace屬性
       * <mapper namespace="com.tuling.mapper.EmployeeMapper">
       */
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        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 type="org.mybatis.caches.ehcache.EhcacheCache"></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);
    }
  }
           

(3)解析mapper.xml中配置的sql語句

将sql中的内容解析成MappedStatement對象,然後存入configuration對象中。

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:  是否需要處理嵌套查詢結果 group by (使用極少)
     * 可以将比如 30條資料的三組資料  組成一個嵌套的查詢結果
     */
    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" >
     <settings>
          <setting name="defaultScriptingLanguage" value="tulingLang"/>
     </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.tuling.mapper.Employee.saveEmployee!selectKey
     */
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    /**
     *<insert id="saveEmployee" parameterType="com.tuling.entity.Employee" useGeneratedKeys="true" keyProperty="id">
     *判斷我們全局的配置類configuration中是否包含以及解析過的元件生成器對象
     */
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {

      /**
       * 若我們配置了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);
  }
           

根據解析後的configuration對象生成sqlsessionfactory對象

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }