1. 程式人生 > 實用技巧 >myBatis原始碼解析-配置檔案解析(6)

myBatis原始碼解析-配置檔案解析(6)

前言

本來打算此次寫一篇關於SqlSession的解析,但發現SqlSession涉及的知識太多。所以先結合mybatis配置檔案(我們專案中常寫的如mybatisConfig.xml),來分析下mybatis初始化時做了些什麼,進而分析語句的執行。此篇原始碼解讀主要結合官網mybatis文件,來進行分析。

原始碼解析

mybatis初始化時,會構建一個全域性的Configuration類,包含了mybatis的配置資訊。檢視mybatis配置檔案頂層結構如下。接下來一個個配置項進行分析。

configuration(配置)
  properties(屬性)
  settings(設定)
  typeAliases(類型別名)

  typeHandlers(型別處理器)
  objectFactory(物件工廠)
  plugins(外掛)
  environments(環境配置)
    environment(環境變數)
    transactionManager(事務管理器)
    dataSource(資料來源)
  databaseIdProvider(資料庫廠商標識)
  mappers(對映器)

properties(屬性)

屬性既可以在外部檔案中指定(resource / url),也可以在</propertie>標籤中指定。例如:

<properties resource="com/xiaobing/resource/jdbcConfig.properties">
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/auth" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</properties>

其中jdbcConfig.properties檔案中也可以指定屬性,例如:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/auth

配置好了這些屬性,可以在後面的配置檔案中使用這些值,如${driver}:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

那此時有一個問題,如果我在</propertie>標籤中指定了屬性值,又在外部檔案中指定了相同的屬性值,如上例子中的resource指向的檔案。那木mybatis優先會使用哪一種。分析原始碼。關於配置檔案解析,直接定位在XMLConfigBuilder中的parseConfiguration(XNode root)方法,為什麼會到這個檔案,後面分析SqlSession時會進行解釋。

private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 讀取properties配置
      typeAliasesElement(root.evalNode("typeAliases")); // 讀取別名設定
      pluginElement(root.evalNode("plugins")); // 讀取外掛設定
      objectFactoryElement(root.evalNode("objectFactory")); // 讀取物件工廠設定
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 讀取物件包裝工廠設定
      settingsElement(root.evalNode("settings")); // 讀取setting設定
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 // 讀取環境設定
      databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 讀取資料庫ID提供資訊
      typeHandlerElement(root.evalNode("typeHandlers"));  // 讀取型別轉換處理器
      mapperElement(root.evalNode("mappers"));  // 讀取Sql
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

此方法包含了所有配置檔案的解析。分析propertiesElement()屬性解析方法。

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties(); // 獲取properties中的鍵值對
      String resource = context.getStringAttribute("resource"); // 獲取resource指定的檔案地址
      String url = context.getStringAttribute("url"); // 獲取url指定的檔案地址
      if (resource != null && url != null) { // resource和url不能同時存在
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));  // 此處使用putAll.如果出現相同屬性,則會覆蓋
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url)); // 同理
      }
      Properties vars = configuration.getVariables(); // configuration中設定的properties
      if (vars != null) {
        defaults.putAll(vars); // 同理
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);  // 將最終的properties放入configuration配置類中
    }
}

分析可得,先解析</propertie>標籤的屬性值,在解析屬性檔案中的屬性值,在解析設定configuration時預設值的propertie值,逐層覆蓋。所以可得出優先順序,configuration時預設值的propertie值 > 屬性檔案中的屬性值 > </propertie>標籤的屬性值;這也解釋了網上關於properties屬性優先順序排序。

settings(設定)

此處關於settings的配置實在太多,還有些配置未列出來。詳情資訊如下。

設定名

描述

有效值

預設值

cacheEnabled

全域性性地開啟或關閉所有對映器配置檔案中已配置的任何快取。

true | false

true

lazyLoadingEnabled

延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入。 特定關聯關係中可通過設定fetchType屬性來覆蓋該項的開關狀態。

true | false

false

aggressiveLazyLoading

開啟時,任一方法的呼叫都會載入該物件的所有延遲載入屬性。 否則,每個延遲載入屬性會按需載入(參考lazyLoadTriggerMethods)。

true | false

false (在 3.4.1 及之前的版本中預設為 true)

multipleResultSetsEnabled

是否允許單個語句返回多結果集(需要資料庫驅動支援)。

true | false

true

useColumnLabel

使用列標籤代替列名。實際表現依賴於資料庫驅動,具體可參考資料庫驅動的相關文件,或通過對比測試來觀察。

true | false

true

useGeneratedKeys

允許 JDBC 支援自動生成主鍵,需要資料庫驅動支援。如果設定為 true,將強制使用自動生成主鍵。儘管一些資料庫驅動不支援此特性,但仍可正常工作(如 Derby)。

true | false

False

autoMappingBehavior

指定 MyBatis 應如何自動對映列到欄位或屬性。 NONE 表示關閉自動對映;PARTIAL 只會自動對映沒有定義巢狀結果對映的欄位。 FULL 會自動對映任何複雜的結果集(無論是否巢狀)。

NONE, PARTIAL, FULL

PARTIAL

autoMappingUnknownColumnBehavior

指定發現自動對映目標未知列(或未知屬性型別)的行為。

  • NONE: 不做任何反應
  • WARNING: 輸出警告日誌('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'的日誌等級必須設定為WARN
  • FAILING: 對映失敗 (丟擲SqlSessionException)

NONE, WARNING, FAILING

NONE

defaultExecutorType

配置預設的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); BATCH 執行器不僅重用語句還會執行批量更新。

SIMPLE REUSE BATCH

SIMPLE

defaultStatementTimeout

設定超時時間,它決定資料庫驅動等待資料庫響應的秒數。

任意正整數

未設定 (null)

defaultFetchSize

為驅動的結果集獲取數量(fetchSize)設定一個建議值。此引數只可以在查詢設定中被覆蓋。

任意正整數

未設定 (null)

defaultResultSetType

指定語句預設的滾動策略。(新增於 3.5.2)

FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同於未設定)

未設定 (null)

safeRowBoundsEnabled

是否允許在巢狀語句中使用分頁(RowBounds)。如果允許使用則設定為 false。

true | false

False

safeResultHandlerEnabled

是否允許在巢狀語句中使用結果處理器(ResultHandler)。如果允許使用則設定為 false。

true | false

True

mapUnderscoreToCamelCase

是否開啟駝峰命名自動對映,即從經典資料庫列名 A_COLUMN 對映到經典 Java 屬性名 aColumn。

true | false

False

localCacheScope

MyBatis 利用本地快取機制(Local Cache)防止迴圈引用和加速重複的巢狀查詢。 預設值為 SESSION,會快取一個會話中執行的所有查詢。 若設定值為 STATEMENT,本地快取將僅用於執行語句,對相同 SqlSession 的不同查詢將不會進行快取。

SESSION | STATEMENT

SESSION

jdbcTypeForNull

當沒有為引數指定特定的 JDBC 型別時,空值的預設 JDBC 型別。 某些資料庫驅動需要指定列的 JDBC 型別,多數情況直接用一般型別即可,比如 NULL、VARCHAR 或 OTHER。

JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。

OTHER

azyLoadTriggerMethods

指定物件的哪些方法觸發一次延遲載入。

用逗號分隔的方法列表。

equals,clone,hashCode,toString

配置檔案中一個完整<settings>元素例項如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

關於原始碼解析,此處實際開發中這些屬性用到較少,自己比較感興趣的是關於mybatis二級快取的配置,後面會出一篇部落格介紹cacheEnabled設定。解析settings原始碼如下,內容太多,就不一一分析了,感興趣的可以自己檢視。

private void settingsElement(XNode context) throws Exception { // 解析settings
    if (context != null) {
      Properties props = context.getChildrenAsProperties();
      // Check that all settings are known to the configuration class
      MetaClass metaConfig = MetaClass.forClass(Configuration.class);
      for (Object key : props.keySet()) {
        if (!metaConfig.hasSetter(String.valueOf(key))) {
          throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
      }
      configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
      configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
      configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
      configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
      configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
      configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
      configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
      configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
      configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
      configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
      configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
      configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
      configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
      configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
      configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
      configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
      configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
      configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
      configuration.setLogPrefix(props.getProperty("logPrefix"));
      configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
      configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
  }

typeAliases(類型別名)

typeAliases用於對一個型別名進行別名設定,意在降低冗餘的全限定類名書寫。例如:

<typeAliases>
        <typeAlias type="com.xiaobing.pojo.SysUser" alias="_user"></typeAlias>
</typeAliases>

使用時用_user代替com.xiaobing.pojo.SysUser。除了支援單條語句的定義,還支援在包下掃描需要設定別名的類。例如:

<typeAliases>
    <package name="com.xiaobing.pojo"/>
</typeAliases>
package com.xiaobing.pojo;

import org.apache.ibatis.type.Alias;

@Alias("sysDept")
public class SysDept {
}

使用註解@Alias就能將此類的全限定類名設定一個別名。分析解析typeAliases原始碼:

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) { // 是否是package
          String typeAliasPackage = child.getStringAttribute("name"); // 獲得包名
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);  // 去指定包下檢視使用@Alias了註解的類,進行類型別名註冊
        } else { // 單個型別的註冊
          String alias = child.getStringAttribute("alias");  // 獲得別名
          String type = child.getStringAttribute("type"); // 獲得型別
          try {
            Class<?> clazz = Resources.classForName(type); // 轉為class物件
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz); // 進行類型別名註冊
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
}

別名註冊用到了TypeAliasRegistry類,在分析型別轉換器時忘了介紹這個類,這個類其實存放了一個map,用於存放別名對應的真實class類。在初始化時放置了常用的別名與之對應的class類。

typeHandlers(型別處理器)

型別處理器專門寫了一篇部落格去介紹,此處不做詳解,見上篇文章。配置方式如下:

<!-- mybatisConfig.xml -->
<typeHandlers>
    <typeHandler handler="com.xiaobing.custom.config.CustomTypeHandle"/>
</typeHandlers>
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(String.class)
public class CustomTypeHandle extends BaseTypeHandler<String>{

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, java.lang.String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i,parameter);
    }

    @Override
    public java.lang.String getNullableResult(ResultSet rs, java.lang.String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    @Override
    public java.lang.String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    @Override
    public java.lang.String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex);
    }
}

使用者自定義了一個型別處理器,Java型別是String,Jdbc型別是VARCHAR。上篇部落格關於註冊型別處理器的過程,為先從TYPE_HANDLER_MAP中拿出Java型別對應的key為Jdbc型別value為型別處理器的map,然後在該map中放置自定義的Jdbc型別,型別處理器。注意此時使用的是map.put(k,v)。也就是說,型別註冊器中對於一個java型別和一個jdbc型別,只存在唯一的型別處理器。所以上文自定義的Java型別是String,Jdbc型別是VARCHAR的型別處理器會覆蓋預設的Java型別是String,Jdbc型別是VARCHAR的型別處理器。此處也講清楚了網上說的自定義型別處理器將會覆蓋已有的型別處理器。若不清楚,建議回看上篇博文。
分析解析typeHandlers原始碼:

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) { // 整個包註冊
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else { // 單類註冊
          String javaTypeName = child.getStringAttribute("javaType"); // 獲取型別轉換需轉換的java名稱
          String jdbcTypeName = child.getStringAttribute("jdbcType"); // 獲取型別轉換需轉換的jdbc名稱
          String handlerTypeName = child.getStringAttribute("handler"); // 獲取轉化器名稱
          Class<?> javaTypeClass = resolveClass(javaTypeName); // 獲取java型別的class,此處可以是設定的別名
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);  // 獲取jdbc型別
          Class<?> typeHandlerClass = resolveClass(handlerTypeName); // 獲取型別處理器的class
          if (javaTypeClass != null) {  // 三種註冊方式,見上篇部落格關於typeHandle分析
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
}

如解析配置檔案時可見,在<typeHandler/>標籤中除了指定型別處理器所在包外,還可以設定jdbc型別和java型別,此處設定了就自動忽略型別處理器所在類上的@MappedJdbcTypes,@MappedTypes註解了。因為都一個意思。

objectFactory(物件工廠)

每次 MyBatis 建立結果物件的新例項時,它都會使用一個物件工廠(ObjectFactory)例項來完成例項化工作。預設的物件工廠需要做的僅僅是例項化目標類,要麼通過預設無參構造方法,要麼通過存在的引數對映來呼叫帶有引數的構造方法。 如果想覆蓋物件工廠的預設行為,可以通過建立自己的物件工廠來實現。比如:

<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}

DefaultObjectFactory類也比較簡單,分析下吧。

public class DefaultObjectFactory implements ObjectFactory, Serializable {

  private static final long serialVersionUID = -8855120656740914948L;

  public <T> T create(Class<T> type) {
    return create(type, null, null);
  }
  // 根據類名建立物件 constructorArgTypes建構函式引數型別列表 constructorArgs建構函式引數列表
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Class<?> classToCreate = resolveInterface(type);
    @SuppressWarnings("unchecked")
    // we know types are assignable
    T created = (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
    return created;
  }

  public void setProperties(Properties properties) {
    // no props for default
  }
  // 利用class 的反射去建立物件例項
  private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor(); // 獲取無參構造方法
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true); // 設定可寫
        }
        return constructor.newInstance(); // 例項化
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) { // 若有異常,丟擲
      StringBuilder argTypes = new StringBuilder();
      if (constructorArgTypes != null) {
        for (Class<?> argType : constructorArgTypes) {
          argTypes.append(argType.getSimpleName());
          argTypes.append(",");
        }
      }
      StringBuilder argValues = new StringBuilder();
      if (constructorArgs != null) {
        for (Object argValue : constructorArgs) {
          argValues.append(String.valueOf(argValue));
          argValues.append(",");
        }
      }
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }
 ......
}

如前文所講,主要通過類的反射到構造方法,再去建立例項。

plugins(外掛)

外掛使用較少,不太熟悉,按照官方文件的解釋。有點類似於攔截器,在某個動作執行前後做屬於自己的業務操作。MyBatis 允許你在對映語句執行過程中的某一點進行攔截呼叫。預設情況下,MyBatis 允許使用外掛來攔截的方法呼叫包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
這些類中方法的細節可以通過檢視每個方法的簽名來發現,或者直接檢視 MyBatis 發行包中的原始碼。 如果你想做的不僅僅是監控方法的呼叫,那麼你最好相當瞭解要重寫的方法的行為。 因為在試圖修改或重寫已有方法的行為時,很可能會破壞 MyBatis 的核心模組。 這些都是更底層的類和方法,所以使用外掛的時候要特別當心。

通過 MyBatis 提供的強大機制,使用外掛是非常簡單的,只需實現 Interceptor 介面,並指定想要攔截的方法簽名即可。

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>
// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}

檢視解析plugins的原始碼如下:

private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
  for (XNode child : parent.getChildren()) {
    String interceptor = child.getStringAttribute("interceptor"); // 獲取外掛攔截器
    Properties properties = child.getChildrenAsProperties(); // 外掛設定的properties值
    Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // 例項化
    interceptorInstance.setProperties(properties); // 放入外掛攔截器
    configuration.addInterceptor(interceptorInstance); // 新增到configuration中
  }
}
}

environments(環境配置)

environments允許使用者設定多套環境,如開發環境,生成環境。

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="${driver}" />
            <property name="url" value="${url}" />
            <property name="username" value="${username}" />
            <property name="password" value="${password}" />
        </dataSource>
    </environment>
    <environment id="develop">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="${driver}" />
            <property name="url" value="${url}" />
            <property name="username" value="${username}" />
            <property name="password" value="${password}" />
        </dataSource>
    </environment>
</environments>

注意配置時需要配置事務型別管理器,資料來源。

事務型別處理器mybatis支援JDBC和MANAGED兩種型別,其中JDBC是直接使用資料庫的事務管理機制。MANAGED為空的實現,主要依賴於容器的事務管理,後面會出一個分析Spring模組的事務配置。對於資料來源詳情,請看我前面關於資料來源分析的部落格。此處,不做解釋。

分析解析environments原始碼:

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {   // 沒有指定environment,則選擇預設環境
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) { // 找到設定的environment
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 初始化事務工廠
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // 初始化資料來源工廠
          DataSource dataSource = dsFactory.getDataSource(); // 獲得資料來源
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build()); // 放入全域性配置類中
        }
      }
    }
}

databaseIdProvider(資料庫廠商標識)

暫且略過,以後有時間在補充(懶癌發作了)

mappers(對映器)

Sql對映檔案詳解,mybatis關鍵,後文會著重分析sql對映檔案。(懶癌發作了--)