Mybatis源碼解析(二) —— 加載 Configuration

Mybatis源碼解析(二) —— 加載 Configuration

   正如上文所看到的 Configuration 對象保存了全部Mybatis的配置信息,也就是說mybatis-config.xml 以及 mapper.xml 中的全部信息均可以在 Configuration 對象中獲取到。因此通常狀況下,Configuration 對象只會存在一個。經過上篇文章咱們知道了mybatis-config.xml 和 mapper.xml分別是經過 XMLConfigBuilder 和 XMLMapperBuilder 進行解析存儲到Configuration的。但有2個問題須要咱們去了解:java

  • 一、 XMLConfigBuilder 和 XMLMapperBuilder 是如何解析xml信息的?
  • 二、 Configuration 內部結構是怎樣的?它是如何存儲 xml信息的?

   那麼,咱們就帶着這2個問題去分析下源碼吧!sql

1、 Configuration 屬性

  Configuration包含了會深深影響 MyBatis 行爲的設置和屬性信息。 配置文檔的頂層結構以下:數據庫

  • configuration
    • properties(屬性)
    • settings(設置)
    • typeAliases(類型別名)
    • typeHandlers(類型處理器)
    • objectFactory(對象工廠)
    • plugins(插件)
    • environments(環境配置)
      • environment(環境變量)
      • transactionManager(事務管理器)
      • dataSource(數據源)
    • databaseIdProvider(數據庫廠商標識)
    • mappers(映射器)

  其中咱們最常配置的是 settings 。一個配置相對完整的 settings 元素的示例以下:apache

<settings>
  <!-- 全局地開啓或關閉配置文件中的全部映射器已經配置的任何緩存 -->
  <setting name="cacheEnabled" value="true"/>
  <!-- 延遲加載的全局開關。當開啓時,全部關聯對象都會延遲加載。 特定關聯關係中可經過設置 fetchType 屬性來覆蓋該項的開關狀態。 -->
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- 是否容許單一語句返回多結果集(須要驅動支持) -->
  <setting name="multipleResultSetsEnabled" value="true"/>
  <!-- 使用列標籤代替列名 -->
  <setting name="useColumnLabel" value="true"/>
  <!-- 容許 JDBC 支持自動生成主鍵,須要驅動支持。 若是設置爲 true 則這個設置強制使用自動生成主鍵,儘管一些驅動不能支持但仍可正常工做(好比 Derby) -->
  <setting name="useGeneratedKeys" value="false"/>
  <!-- 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示取消自動映射;PARTIAL 只會自動映射沒有定義嵌套結果集映射的結果集。 FULL 會自動映射任意複雜的結果集(不管是否嵌套)-->
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <!-- 指定發現自動映射目標未知列(或者未知屬性類型)的行爲。
       NONE: 不作任何反應
       WARNING: 輸出提醒日誌 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日誌等級必須設置爲 WARN)
       FAILING: 映射失敗 (拋出 SqlSessionException) -->
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <!-- 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新 -->
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <!-- 設置超時時間,它決定驅動等待數據庫響應的秒數 -->
  <setting name="defaultStatementTimeout" value="25"/>
  <!-- 爲驅動的結果集獲取數量(fetchSize)設置一個提示值。此參數只能夠在查詢設置中被覆蓋 -->
  <setting name="defaultFetchSize" value="100"/>
  <!-- 容許在嵌套語句中使用分頁(RowBounds)。若是容許使用則設置爲 false-->
  <setting name="safeRowBoundsEnabled" value="false"/>
  <!-- 是否開啓自動駝峯命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的相似映射 -->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <!-- MyBatis 利用本地緩存機制(Local Cache)防止循環引用(circular references)和加速重複嵌套查詢。 默認值爲 SESSION,這種狀況下會緩存一個會話中執行的全部查詢。 若設置值爲 STATEMENT,本地會話僅用在語句執行上,對相同 SqlSession 的不一樣調用將不會共享數據-->
  <setting name="localCacheScope" value="SESSION"/>
  <!-- 當沒有爲參數提供特定的 JDBC 類型時,爲空值指定 JDBC 類型。 某些驅動須要指定列的 JDBC 類型,多數狀況直接用通常類型便可,好比 NULL、VARCHAR 或 OTHER-->
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <!-- 指定哪一個對象的方法觸發一次延遲加載-->
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
  <!-- 指定 MyBatis 所用日誌的具體實現,未指定時將自動查找。-->
  <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
複製代碼

   查看 Configuration 源碼,咱們能夠輕鬆的找到上面配置對應的字段屬性:緩存

protected Environment environment;
 
   protected boolean safeRowBoundsEnabled = false;
   protected boolean safeResultHandlerEnabled = true;
   protected boolean mapUnderscoreToCamelCase = false;
   protected boolean aggressiveLazyLoading = true;
   protected boolean multipleResultSetsEnabled = true;
   protected boolean useGeneratedKeys = false;
   protected boolean useColumnLabel = true;
   protected boolean cacheEnabled = true;
   protected boolean callSettersOnNulls = false;
   protected String logPrefix;
   protected Class <? extends Log> logImpl;
   protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
   protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
   protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
   protected Integer defaultStatementTimeout;
   protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
   protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
 
   protected Properties variables = new Properties();
   protected ObjectFactory objectFactory = new DefaultObjectFactory();
   protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
   protected MapperRegistry mapperRegistry = new MapperRegistry(this);
 
   protected boolean lazyLoadingEnabled = false;
   protected ProxyFactory proxyFactory;
 
   protected String databaseId;
   
   protected Class<?> configurationFactory;
 
   protected final InterceptorChain interceptorChain = new InterceptorChain();
   protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
   protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
   protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
複製代碼

   上面的字段屬性對應的是mybatis-config.xml 配置文件,那麼對應mapper.xml的字段屬性以下:session

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
  
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
複製代碼

  其中 咱們最須要關注的是 mappedStatementsresultMaps 以及 sqlFragmentsmybatis

  • resultMaps: 不難理解就是 保存了 mapper.xml 中的 resultMap 節點信息
  • mappedStatements: 保存了 Mapper 配置文件中得 select/update/insert/delete節點信息
  • sqlFragments: 保存了 Mapper 配置文件中得 sql 節點信息

  上面3種是咱們在平時項目開發中使用最多的,咱們能夠發現其 都是 StrictMap 這個 內部類 的 value,那咱們來具體分析下 StrictMap 與普通Map有什麼不同的地方:app

public V put(String key, V value) {
      if (containsKey(key))
        throw new IllegalArgumentException(name + " already contains value for " + key);
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          // 存簡稱 key
          super.put(shortKey, value);
        } else {
          // 重複的 key 時存的value 爲  Ambiguity ,在 get 時會判斷  value 是否爲 Ambiguity,是則拋異常
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      // 存全稱 key
      return super.put(key, value);
    }
    
    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      // 判斷類型是否爲 Ambiguity
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }
        
   
    // 截取最後一個"."符號後面的字符串作爲shortName
    private String getShortName(String key) {
      final String[] keyparts = key.split("\\.");
      final String shortKey = keyparts[keyparts.length - 1];
      return shortKey;
    }    
    複製代碼

   從源碼中咱們能夠看出,其重寫了 put 和 get 2個方法 ,其中 put方法針對 key 作了3個方面的處理:ide

  • 一、 截取最後一個"."符號後面的字符串作爲 key 存儲一次value
  • 二、 key 重複時,存儲的value是一個 Ambiguity 對象。get 獲取時會判斷value是否未 Ambiguity 類型,若是是則拋出異常
  • 三、 直接調用 super.put(key, value) 存儲同一 value 一次(此時key未作任何操做處理)

   也就是說,針對 :源碼分析

StrictMap.put("com.xxx.selectId","select * from user where id=?")複製代碼

   這一次put請求, StrictMap 中有 2個不一樣的key,但value相同的元素:

com.xxx.selectId = select * from user where id=?
          selectId = select * from user where id=?
          複製代碼

  以上就是 Configuration 內部屬性的大體分析,其中關鍵的屬性分別是: mappedStatementsresultMapssqlFragments,接下來咱們會分析這3。

2、 XMLConfigBuilder.parse()

   XMLConfigBuilder.parse() 主要用於解析mybatis-config.xml 配置文件的信息,其自己沒有多大的意義去分析,我這邊仍是給出部分源碼吧,有想進一步去了解的同窗能夠自行深刻分析:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      // 解析 properties(參數配置) 節點
      propertiesElement(root.evalNode("properties"));
      // 解析 typeAliases(別名) 節點
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析 plugins(插件) 節點
      pluginElement(root.evalNode("plugins"));
      // 解析 objectFactory(數據庫返回結果集使用) 節點
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析 settings 節點
      settingsElement(root.evalNode("settings"));
      // 解析 environments 節點
      environmentsElement(root.evalNode("environments")); 
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析 mappers 節點
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
複製代碼

3、 XmlMapperBuilder.parse()

   若是拿電腦作比喻的話,前面 XMLConfigBuilder.parse() 就比如主機,可是僅僅有主機是不行的,咱們還得須要鼠標、鍵盤、顯示器等等組件才玩組裝成一個完整的電腦。而 XmlMapperBuilder.parse() 所作的事兒就是組裝各類組件(Mapper)。咱們來看下 XmlMapperBuilder.parse() 的源碼:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析方法源頭
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

  // 最核心解析方法
  private void configurationElement(XNode context) {
    try {
      // 咱們都知道 Mapper 的  namespace 是與 Mapper接口路徑對應的,因此進來須要判斷下 namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
          throw new BuilderException("Mapper's namespace cannot be empty");
      } 
      // 爲 MapperBuilderAssistant 設置 namespace
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析 resultMap 節點信息
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析 sql 節點信息
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析 select|insert|update|delete 節點信息
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
複製代碼

   咱們能夠看到核心的解析方法內部分別針對 不一樣的節點 進行加載,咱們先看下 resultMap 的解析加載:

加載 resultMap節點

   正如官方所描述的同樣 : resultMap 是最複雜也是最強大的元素(用來描述如何從數據庫結果集中來加載對象)。 因此其解析複雜度也是最複雜的,咱們先看一個簡單的resultMap 節點配置:

<resultMap id="selectUserById" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <result property="phone" column="phone"/>
</resultMap>
複製代碼

  結合中上面的配置, 咱們再看下面其解析源碼會更加清晰明瞭:

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 獲取 resultMap 節點中的 id 配置信息,也就是上面示列的  id="selectUserById"
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    // 獲取 resultMap 節點中的 type 配置信息,也就是上面示列的  type="User"
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    // 獲取到全部字節的信息,即 id 節點、result節點等等
    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 {
        ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // 將獲取到的 子節點信息封裝到 resultMapping 對象中。
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 實際調用的是 MapperBuilderAssistant.addResultMap() 方法
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
複製代碼

   從源碼分析來看,整個解析流程分4步走:

  • 一、 獲取 resultMap 節點中的 id 配置信息,也就是上面示列的 id="blogPostResult"
  • 二、 獲取 resultMap 節點中的 type 配置信息,也就是上面示列的 type="User" ( Class 名)
  • 三、 將獲取到的 子節點信息封裝到 resultMapping 對象中。
  • 四、 實際調用的是 MapperBuilderAssistant.addResultMap() 方法 將上面3步獲取到的數據生成 resultMap 保存到 Configuration 中。

   上面有2 個關鍵對象: ResultMappingMapperBuilderAssistant。其中 MapperBuilderAssistant 貫穿了整個Mapper的解析,因此先不分析,咱們先看下 ResultMapping ,其源碼內部的字段屬性有如下:

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;
複製代碼

ResultMap 類屬性

private String id;
  private Class<?> type;
  private List<ResultMapping> resultMappings;
  private List<ResultMapping> idResultMappings;
  private List<ResultMapping> constructorResultMappings;
  private List<ResultMapping> propertyResultMappings;
  private Set<String> mappedColumns;
  private Discriminator discriminator;
  private boolean hasNestedResultMaps;
  private boolean hasNestedQueries;
  private Boolean autoMapping;
複製代碼

   相信大多數同窗對其中的 property、column、javaType、jdbcType、typeHandler 這幾個相對熟悉不少,正如看到的同樣 ResultMapping 主要用於封裝 resultMap 節點的全部子節點(子節點嵌套也是同樣的) 的 信息。它與 ResultMap 的關係以下:

  • 一、 ResultMap 是由 id、type以及大量的 ResultMapping 對象 組合而成。
  • 二、 ResultMapping對象 是 結果集(數據庫操做結果集)與 java Bean對象 屬性的 對應關係。 即一個 ResultMapping對象 對應一個 Bean 對象中某個字段屬性。
  • 三、 ResultMap 對象是 結果集 與 java Bean對象的 對應關係。即一個 ResultMap 對象 對應一個 Bean對象。

加載 select|insert|update|delete節點

   咱們先看一個簡單的 select 節點元素的配置:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>
複製代碼

   咱們再來分析下 其實如何被加載的, 咱們來分析下 加載這4個節點的方法 buildStatementFromContext() 源碼:

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 建立 XMLStatementBuilder 
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 經過 XMLStatementBuilder的 parseStatementNode() 方法進行加載。
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
複製代碼

   咱們能夠看到其內部建立了一個 XMLStatementBuilder 對象,而後再調用其 parseStatementNode() 進行加載的。仔細發現,咱們能夠看到建立 XMLStatementBuilder 對象須要 的3跟 關係構造參數:configuration、 builderAssistant(是否是很熟悉,沒錯就是 MapperBuilderAssistant ) 、context(節點信息)

XMLStatementBuilder.parseStatementNode()

   parseStatementNode() 方法是加載 select|insert|update|delete節點 信息的核心,那麼咱們深刻分析其內部實現:

// 省略了一些相對不重要的代碼
   public void parseStatementNode() {
    
    // 如下幾個獲取的配置信息,咱們都應該很熟悉吧
    String id = context.getStringAttribute("id");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);

    
    // 讀取 <include> 信息 
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());


    // 將每一個<select/>, <update/>,<insert/>,<delete/> 加載(經過 createSqlSource()方法)爲一個 DynamicSqlSource (SqlSource 最經常使用子類 ):內部存在 getBoundSql() 方法 會從XML文件讀取的映射語句的內容 並封裝成 BoundSql。
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    .....

    // 經過 MapperBuilderAssistant 的 addMappedStatement() 添加  MappedStatement 信息。
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
複製代碼

   根據源碼咱們大體能夠把整個流程分紅4個部分:

  • 一、 讀取 select|insert|update|delete 節點的屬性信息,好比: id、resultMap等等咱們經常使用到的信息
  • 二、 處理 節點信息 ,經過 XMLIncludeTransformer.applyIncludes() 方法將 Sql 中的 替換成真實的SQl
  • 三、 經過 LanguageDriver.createSqlSource()方法 將每一個 select|insert|update|delete 節點 內部信息(即SQL語句) 加載(經過 )爲一個 SqlSource:內部存在 getBoundSql() 方法會將SQL 替換(主要替換 #{ } 和 ${ }) 並封裝成 BoundSql
  • 四、 經過 MapperBuilderAssistant 的 addMappedStatement() 方法往 Configuration 添加 MappedStatement 信息。

   上面中涉及到了幾個咱們首次見面的類,咱們先從 SqlSource 開始分析,咱們來看下用得最多的子類 DynamicSqlSource 的源碼:

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }
  複製代碼

   其內部是經過 XMLScriptBuilder.parseScriptNode() 方法 來進行建立 SqlSource,繼續查詢該方法內部源碼:

public SqlSource parseScriptNode() {
    // 將一個SQL內容加載成對個 SqlNode
    List<SqlNode> contents = parseDynamicTags(context);
    // 經過組合模式將多個 SqlNode 組合成一個 SqlNode
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
      // 建立動態的 SqlSource
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 建立原始(靜態)的 SqlSource
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
  複製代碼

   上面源碼中關於 SqlNode 以及它的大量子類之間的設計是很了不得,可是因爲篇幅有限,這裏大體解釋下爲何須要把 SQL 語句 拆分紅多個 SqlNode,咱們先看下示列:


<select id="selectStudents" parameterType="int" resultType="hashmap">
        select 
            stud_id as studId, name, phone
        from students
        <where>
            name = #{name}
            <if test="phone != null">
                AND phone = #{phone}
            </if>
        </where>
        
</select>

  複製代碼

   正如上面的sql同樣,若是咱們一次性把SQL保存到一個 SqlNode 中,那是否是在獲取生成 BoundSql 時 解析相對困難了呢?若是咱們把一些關鍵點給作個分割是否是相對好解析點呢?因此出現了 IfSqlNode、WhereSqlNode等等,不用看,你們應該也能明白 IfSqlNod 就是用於 存儲 :

<if test="phone != null">
                AND phone = #{phone}
            </if>複製代碼

   固然不是直接存儲的,大體結構能夠看下圖:

IfSqlNode

   咱們獲取 BoundSql 時 IfSqlNode 就會判斷 是否知足條件。因此整個 SqlNode 體系是很龐大的,它們分別有不一樣的職責。從上面咱們看到最後將 SqlNode 做爲 建立 DynamicSqlSource 對象的參數。 咱們來查看下 DynamicSqlSource 源碼。看看其是如何獲取到 BoundSql的:

public class DynamicSqlSource implements SqlSource {

  private Configuration configuration;
  // 存放了SQL片斷信息
  private SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 每一個SqlNode的 apply方法調用時,都將解析好的sql加到context中,最終經過context.getSql()獲得完整的sql 
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

}複製代碼

   正如上面所示同樣, 每一個SqlNode都會取調用 apply方法 自行解析並拼接到context。可能有人會問,getBoundSql() 何時會被調用呢? 我這裏提早 解釋下: 當咱們請求Mapper接口時(即一個SqlSession會話訪問數據庫時)會調用到。

   BoundSql 內部主要時封裝好了請求sql,以及請求參數,這是其源碼內部屬性:

// 一個完整的sql,此時的sql 就是 JDBC 那種,即  select * from user where id = ?
  private String sql; 
  // 參數列表
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;
複製代碼

   至此, 加載 select|insert|update|delete節點 的流程已經很是清晰了。但咱們還有一個 MapperBuilderAssistant 沒解析。其實 MapperBuilderAssistant 類如其,它就是 MapperBuilderXml 的 助理。MapperBuilderXml 對象負責從XML讀取配置,而 MapperBuilderAssistant 負責建立對象並加載到Configuration中。

4、我的總結

   整個 Configuration 的加載主要分2部分:

  • 一、 mybatis-config.xml 的加載
  • 二、 mapper.xml 的加載

  其中 mapper.xml 的加載 是 最爲複雜的,本文也主要解析了它的加載。下面的序列圖講述其加載流程:

mapper.xml 的加載流程

         若是您對這些感興趣,歡迎star、follow、收藏、轉發給予支持!

> 本文由博客一文多發平臺 [OpenWrite](https://openwrite.cn?from=article_bottom) 發佈!  複製代碼
相關文章
相關標籤/搜索