亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

mybatis核心流程的示例分析

發布時間:2021-12-30 09:53:26 來源:億速云 閱讀:163 作者:小新 欄目:大數據

這篇文章將為大家詳細講解有關mybatis核心流程的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

1. 例子

我們先寫個例子。首先要配置一個資源文件 app.properties,配置一些屬性,比如環境變量。

# 環境配置
env=local

再配置 mybatis-config.xml,這是 mybatis 的配置文件,是配置 mybatis 的各種配置信息,主要有:屬性 properties、全局設置 settings、別名 typeAliases、環境 environments、映射 mappers:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

  <!-- autoMappingBehavior should be set in each test case -->

  <!-- 讀取資源文件-->
  <properties resource="org/apache/ibatis/autoconstructor/app.properties"/>

  <settings>
    <!-- 開啟二級緩存-->
    <setting name="cacheEnabled" value="true"/>
    <!-- 開啟駝峰式命名-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>

  <!-- 別名配置 -->
  <typeAliases>
    <package name="org.apache.ibatis.autoconstructor"/>
  </typeAliases>

  <!-- 環境配置 -->
  <environments default="${env}">

    <environment id="local">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver"/>
        <!-- 此配置是基于內存連接的-->
        <property name="url" value="jdbc:hsqldb:mem:automapping"/>
        <property name="username" value="sa"/>
      </dataSource>
    </environment>

    <environment id="dev">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver"/>
        <!-- 此配置是基于內存連接的-->
        <property name="url" value="jdbc:hsqldb:mem:automapping"/>
        <property name="username" value="sa"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <!-- 掃描指定的映射文件 -->
    <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
  </mappers>

</configuration>

接著配置映射文件 AutoConstructorMapper.xml,它就是寫 SQL 的地方:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper">
  <!--開啟二級緩存-->
  <cache/>
  <!--<select id="selectOneById" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">-->
  <select id="selectOneById" resultType="primitiveSubject">
    SELECT * FROM subject WHERE id = #{id}
  </select>
</mapper>

然后給出基本的 POJO 和 mapper 接口:

public class PrimitiveSubject implements Serializable {
  private final int id;
  private final String name;
  private final int age;
  private final int height;
  private final int weight;
  private final boolean active;
  private final Date dt;

  public PrimitiveSubject(final int id, final String name, final int age, final int height, final int weight, final boolean active, final Date dt) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
    this.active = active;
    this.dt = dt;
  }

  @Override
  public String toString() {
    return "PrimitiveSubject{ hashcode="+ this.hashCode() + ", id=" + id + ", name='" + name + '\'' + ", age=" + age
      + ", height=" + height + ", weight=" + weight + ", active=" + active + ", dt=" + dt + '}';
  }

}

/**
 * mapper 接口
 */
public interface AutoConstructorMapper {
  PrimitiveSubject selectOneById(int id);
}

初始化 SQL 數據 CreateDB.sql

DROP TABLE subject
IF EXISTS;

DROP TABLE extensive_subject
IF EXISTS;

CREATE TABLE subject (
  id     INT NOT NULL,
  name   VARCHAR(20),
  age    INT NOT NULL,
  height INT,
  weight INT,
  active BIT,
  dt     TIMESTAMP
);

INSERT INTO subject VALUES
  (1, 'a', 10, 100, 45, 1, CURRENT_TIMESTAMP),
  (2, 'b', 10, NULL, 45, 1, CURRENT_TIMESTAMP),
  (2, 'c', 10, NULL, NULL, 0, CURRENT_TIMESTAMP);

最后編寫測試類,這個測試類中初始化了 SqlSessionFactory,同時裝配了內存數據庫;它通過 sqlSessionFactory 開啟了一個 SqlSession,然后獲取 AutoConstructorMapper 對象,執行了它的 selectOneById 方法:

class AutoConstructorTest {

  private static SqlSessionFactory sqlSessionFactory;

  @BeforeAll
  static void setUp() throws Exception {
    // create a SqlSessionFactory
    try (
      Reader reader = Resources
        .getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")
    ) {
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    // populate in-memory database
    BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
                           "org/apache/ibatis/autoconstructor/CreateDB.sql");
  }

@Test
  void selectOneById() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      // 測試環境
      Environment environment = sqlSessionFactory.getConfiguration().getEnvironment();
      System.out.println("environment = " + environment.getId());

      final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
      PrimitiveSubject ps1 = mapper.selectOneById(1);
      System.out.println("ps1 = " + ps1);
    }
  }
}

這樣,一個簡單的例子就編寫完畢了。下面我們開始進入 mybatis 的源碼中,探索下它的內部流程機制。

2. 源碼分析

我們將它的源碼分析分為以下幾個流程:

  1. 解析 mybatis-config.xml 文件,構建 Configuration 配置類信息流程;

  2. 解析 mapper.xml 進行構建緩存、映射聲明等流程;

  3. 創建 SqlSession 流程;

  4. 通過 SqlSession 獲取 mapper 接口執行目標方法流程;

下面我們正式開始解析源碼。

2.1 解析 mybatis-config.xml 構建 Configuration 配置類流程

這個流程在上面的例子中的單元測試類代碼中有體現,具體的相關代碼如下:

SqlSessionFactory sqlSessionFactory;
// ...省略...
      try (
      Reader reader = Resources
        .getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")
    ) {
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }
// ...省略...

上面的邏輯是,加載 mybatis-config.xml 文件到一個輸入流中,然后創建一個 SqlSessionFactoryBuilder 對象,進行構建出一個 SqlSessionFactory 實例,這個實例的生命周期非常長,它是隨著應用程序的關閉而關閉的。

我們看下它的源碼:

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

// ...省略無關方法...
  
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      // 創建一個 XMLConfigBuilder 進行解析流,解析為一個 Configuration 實例
      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 {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

// ...省略無關方法...
  
  /**
   * 構建一個 SQLsession 工廠
   * @param config
   * @return
   */
  public SqlSessionFactory build(Configuration config) {
    // 創建一個默認的 SQLsession 工廠
    return new DefaultSqlSessionFactory(config);
  }

}

可以看到,上面的代碼邏輯,主要是創建一個 XMLConfigBuilder 類型的對象,我們看下它的構造器

  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

發現它會創建一個 Configuration 對象,關聯到父類中。看下 Configuration 的構造器:

public Configuration() {
        // 配置各種基礎類的別名
    // 事務管理器
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    // 數據源工廠
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    // 緩存類別名
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    // 日志類別名
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    // 動態代理別名
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    // xml 腳本解析器
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);

  }

可以看到,它的構造器方法中會注冊一些基礎配置的類的別名,這些別名一般是用在 xml 配置文件中的屬性值,后續會根據別名來解析出對應的實際類型。

回過頭來繼續看 XMLConfigBuilder 的解析方法 parse() 方法,這個方法是把 mybatis 的 xml 文件解析成為一個 Configuration 類型,最后再創建一個 DefaultSqlSessionFactory 類型返回。org.apache.ibatis.builder.xml.XMLConfigBuilder#parse :

  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 屬性
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      // 解析設置 setting
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      // 解析自定義日志
      loadCustomLogImpl(settings);
      // 解析類型別名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析插件
      pluginElement(root.evalNode("plugins"));
      // 解析對象工廠
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析對象包裝工廠
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析反射器工廠
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 設置配置元素
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 解析環境
      environmentsElement(root.evalNode("environments"));
      // 解析數據庫 ID 提供者
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析類型處理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析映射文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

上面的代碼也很好理解,主要是針對 mybatis-config.xml 文件中的各個標簽元素進行解析:

  1. 解析 properties 屬性配置;

  2. 解析 setting 屬性配置;

  3. 解析 typeAliases 類型別名配置;

  4. 解析插件 plugins 配置;

  5. 解析 objectFactory 對象工廠配置;

  6. 解析 objectWrapperFactory 對象包裝工廠配置;

  7. 解析 reflectorFactory 反射工廠配置;

  8. 解析 environments 環境配置;

  9. 解析 databaseIdProvider 數據庫 ID 提供者配置;

  10. 解析 typeHandlers 類型處理器配置;

  11. 解析 mappers 映射文件配置。

mybatis核心流程的示例分析

這些解析內容中,mappers 解析最為重要,我們詳細看下它的解析過程。

2.2 解析 mapper.xml 構建映射聲明、緩存等流程

解析 mappers 的邏輯在 org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 方法中:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 解析 package 屬性
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // 解析 resource 屬性
          String resource = child.getStringAttribute("resource");
          // URL 屬性
          String url = child.getStringAttribute("url");
          // class 屬性
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            // resource 不為空,URL 和 class 為空
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // URL 不為空,resource 和 class 為空
            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 不為空,resource 和 URL 為空
            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.");
          }
        }
      }
    }
  }

可以看到這里的邏輯是獲取了 mappers 標簽中子標簽 package 和 mapper,獲取它們的 name、url、class、resource 屬性,進行加載解析對應的 mapper.xml 文件。

流程為:

  1. 如果 package 標簽存在,就獲取其 name 屬性值,即包名,將它放入 configuration 配置中保存起來, 通過 MapperAnnotationBuilder 類進行解析;

  2. 如果 package 不存在,就獲取 mapper 標簽。

    1. 獲取它們的 resource、url、class 屬性,這里進行了判斷,這三個屬性只能存在一個;

    2. 其中 resource 和 url 是通過 XMLMapperBuilder 實例進行解析的;

    3. class 屬性的值也是會放入到 configuration 配置中進行解析并且保存起來,隨后通過 MapperAnnotationBuilder 類進行解析。

2.2.1 XMLMapperBuilder 解析流程

我們這里主要看下 XMLMapperBuilder 類的解析流程。看下它的 parse() 方法,這個方法就是開始了對 mapper.xml 文件進行解析。org.apache.ibatis.builder.xml.XMLMapperBuilder#parse:

  /**
   * 執行解析 mapper.xml 文件
   */
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 配置 mapper 根元素
      configurationElement(parser.evalNode("/mapper"));
      // 保存資源路徑
      configuration.addLoadedResource(resource);
      // 構建命令空間映射
      bindMapperForNamespace();
    }

    // 解析待定的結果集映射
    parsePendingResultMaps();
    // 解析待定的緩存引用
    parsePendingCacheRefs();
    // 解析待定的 SQL 聲明
    parsePendingStatements();
  }

這里執行了以下幾個解析邏輯:

  1. 執行 configurationElement() 方法,解析 mapper 根元素;

  2. 保存資源路徑到 configuration 實例中;

  3. 執行 bindMapperForNamespace() 方法,根據命名空間加載對應的映射接口;

  4. 執行 parsePendingResultMaps() 方法,解析待定的 ResultMap 結果集映射;

  5. 執行 parsePendingCacheRefs() 方法,解析待定的 CacheRef 緩存引用;

  6. 執行 parsePendingStatements(),解析待定的 Statement SQL 聲明。

這主要的方法是 configurationElement(),我們看下它的邏輯 org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement:

private void configurationElement(XNode context) {
    try {
      // 構建命名空間
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 構建緩存引用 cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      // 構建二級緩存 cache
      cacheElement(context.evalNode("cache"));
      // 構建 parameterMap
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 構建 resultMap
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 構建 SQL 語句
      sqlElement(context.evalNodes("/mapper/sql"));
      // 構建 SQL 語句聲明
      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);
    }
  }

它主要執行的邏輯是:

  1. 構建緩存引用 cache-ref 元素;

  2. 構建二級緩存 cache 元素;

  3. 構建 parameterMap 元素;

  4. 構建 resultMap 元素;

  5. 構建 SQL 元素;

  6. 構建 SQL 語句聲明(解析 select|insert|update|delete 標簽,這一步最為重要);

mybatis核心流程的示例分析

2.2.2 cacheElement() 構建二級緩存

接著我們看下它的構建二級緩存的流程。它是在 org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement 方法中實現的:

  /**
   * 構建二級緩存 cache 元素
   *
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 配置默認的 cache 類型
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 過期策略
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      // 刷新時間
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 緩存大小
      Integer size = context.getIntAttribute("size");
      // 是否只讀,默認是 false,即
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 是否阻塞,為了解決緩存擊穿問題(同一時刻出現大量的訪問同一個數據的請求)
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 其他屬性
      Properties props = context.getChildrenAsProperties();
      // 構建緩存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

注意這里的 cache 標簽,是在 mapper.xml 文件中聲明的。它的邏輯:

  1. 獲取 cache 標簽的類型 type 屬性值,默認為 PERPETUAL,它對應 PerpetualCache 類型;

  2. 獲取過期策略 eviction 屬性值,默認為 LRU 最近最少過期策略,它對應 LruCache 類型;

  3. 獲取刷新時間 flushInterval 屬性值;

  4. 獲取緩存大小 size 屬性值;

  5. 獲取是否只讀 readOnly 屬性值,默認是 false,如果設置了 true,那么就需要 POJO 實現 Serializable 接口;

  6. 獲取是否阻塞 blocking 屬性值,這是用來解決緩存擊穿問題的,稍后將構建緩存時會具體講解;

  7. 獲取以及其他屬性;

  8. 通過調用 MapperBuilderAssistant 映射構建器輔助器的 useNewCache() 方法來構建緩存。

我們看下 MapperBuilderAssistant 映射構建器輔助器的 useNewCache() 方法,org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache:

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 緩存構建器
    Cache cache = new CacheBuilder(currentNamespace)
        // 這里默認使用 PerpetualCache 緩存類型實現,具體的緩存實現類
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        // 添加 LruCache 緩存裝飾器
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        // 開始構建緩存
        .build();
    // 把緩存放入配置類中
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

這里又用到了 CacheBuilder 緩存構建器來構建緩存,,可以看到緩存使用 PerpetualCache 類型實現,并且添加了一個 添加 LruCache 緩存裝飾器來裝飾緩存,看下它的 build 方法 org.apache.ibatis.mapping.CacheBuilder#build:

  /**
   * 構建一個緩存
   *
   * @return
   */
  public Cache build() {
    // 設置默認實現類,和初始化的裝飾器 LruCache
    setDefaultImplementations();
    // 通過反射創建一個 PerpetualCache 對象
    Cache cache = newBaseCacheInstance(implementation, id);
    // 設置緩存屬性
    setCacheProperties(cache);
    // 不要為自定義的緩存應用裝飾器
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      // 如果是 PerpetualCache 類型的緩存,那么就給它設置裝飾器
      for (Class<? extends Cache> decorator : decorators) {
        // 創建一個緩存裝飾器實例
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      // 設置其他標準的裝飾器
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  /**
   * 設置緩存的默認實現
   */
  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

  /**
   * 設置標準的緩存裝飾器
   *
   * @param cache
   * @return
   */
  private Cache setStandardDecorators(Cache cache) {
    try {
      // 獲取緩存的元對象
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      // 設置元數據的信息
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        // 根據清除間隔屬性,設置定時刷新緩存的裝飾器緩存 ScheduledCache
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        // 根據是否可讀寫屬性,設置序列化緩存裝飾器 SerializedCache
        cache = new SerializedCache(cache);
      }
      // 設置日志緩存裝飾器 LoggingCache
      cache = new LoggingCache(cache);
      // 設置同步緩存裝飾器 SynchronizedCache
      cache = new SynchronizedCache(cache);
      if (blocking) {
        // 根據是否阻塞,設置阻塞緩存裝飾器
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

梳理下這里的邏輯:

  1. 執行 setDefaultImplementations() 方法,如果沒有實現類,那就設置默認的實現類 PerpetualCache,添加裝飾器 LruCache;

  2. 通過反射創建一個 Cache 實現類的實例;

  3. 如果緩存實例是 PerpetualCache 類型的,則遍歷裝飾器集合,通過反射創建裝飾器實例,并且執行 setStandardDecorators() 方法為緩存實例設置其他標準的裝飾器;這里的邏輯有:

    1. 獲取緩存的元對象,這是 size 屬性;

    2. 根據 flushInterval 刷新間隔屬性,設置 ScheduledCache 定時刷新緩存的裝飾器對緩存進行裝飾;

    3. 根據 readWrite 是否可讀寫屬性,設置 SerializedCache 序列化緩存裝飾器對緩存進行裝飾;

    4. 設置 LoggingCache 日志緩存裝飾器對緩存進行裝飾;

    5. 設置 SynchronizedCache 同步緩存裝飾器對緩存進行裝飾;

    6. 根據 blocking 是否阻塞屬性,設置 BlockingCache 阻塞緩存裝飾器對緩存進行裝飾;

  4. 如果緩存實例不是 LoggingCache 類型,那就設置 LoggingCache 日志緩存裝飾器對緩存進行裝飾;

  5. 返回緩存實例。

可以看到這里是創建了二級緩存 Cache 接口實例,這里有很多 Cache 裝飾器,下面我們深入其中研究下。

2.2.2.1 Cache 接口

我們先看下 Cache 接口的類圖:

mybatis核心流程的示例分析

可以看到 Cache 接口有多個實現。

2.2.2.2 PerpetualCache 緩存

上面構建緩存的流程中,我們看到了它首先會創建具體的真正存數據的緩存實例 PerpetualCache,看下它的實現:

/**
 * 永久緩存,用于一級緩存
 *
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private final String id;

  /**
   * 使用一個 hashmap 作為緩存
   */
  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

它有兩個屬性,String 類型的 id 屬性、和一個 HashMap 類型的 cache 屬性,可以看到查詢的數據會存儲到這個 cache 屬性中。

2.2.2.3 LruCache 緩存裝飾器

接著它會創建一個 LruCache 緩存對 PerpetualCache 實例進行包裝,LruCache 的實現如下:

/**
 * Lru (least recently used) cache decorator.
 *
 * @author Clinton Begin
 */
public class LruCache implements Cache {

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  public void setSize(final int size) {
    // 重寫 LinkedHashMap 的 removeEldestEntry() 方法,實現 LRU 算法
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
    // 這里獲取 key 是為了讓 key 保持最新,不至于被 LRU 清除掉
    keyMap.get(key); // touch
    return delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

可以看到,它持有一個緩存實例 Cache 類型的 delegate 屬性,這是一個委派的緩存實例;還有持有一個重寫了 LinkedHashMap 類的 keyMap 屬性,它重寫了 removeEldestEntry() 方法,實現了 LRU 最近最少使用算法;同時還持有一個年級最長的 Object 類型的 key。

當有新的數據要放入緩存時,并且 keyMap 中的數據已經滿了的時候,會把年級最長的緩存 key 刪除掉,再存入新的數據。

2.2.2.4 ScheduledCache 緩存裝飾器

接著看 ScheduledCache 定時刷新緩存裝飾器:

public class ScheduledCache implements Cache {

  private final Cache delegate;
  protected long clearInterval;
  protected long lastClear;

  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    this.clearInterval = TimeUnit.HOURS.toMillis(1);
    this.lastClear = System.currentTimeMillis();
  }

  public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    clearWhenStale();
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    clearWhenStale();
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    return clearWhenStale() ? null : delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    clearWhenStale();
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    lastClear = System.currentTimeMillis();
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private boolean clearWhenStale() {
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

}

這個類同樣也是持有一個委派的 Cache 實例,并且它提供了一個 clearWhenStale() 方法。這個方法會根據當前時間、上次清理的時間,與配置的刷新的間隔時間進行判斷,是否需要清理緩存。與當前時間,在獲取緩存數據、保存緩存數據、移除緩存數據、查詢緩存數據數量的時候進行調用。

2.2.2.5 SerializedCache 緩存裝飾器

接著看 SerializedCache 類:

public class SerializedCache implements Cache {

  private final Cache delegate;

  public SerializedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    return object == null ? null : deserialize((byte[]) object);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private byte[] serialize(Serializable value) {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos)) {
      oos.writeObject(value);
      oos.flush();
      return bos.toByteArray();
    } catch (Exception e) {
      throw new CacheException("Error serializing object.  Cause: " + e, e);
    }
  }

  private Serializable deserialize(byte[] value) {
    Serializable result;
    try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
        ObjectInputStream ois = new CustomObjectInputStream(bis)) {
      result = (Serializable) ois.readObject();
    } catch (Exception e) {
      throw new CacheException("Error deserializing object.  Cause: " + e, e);
    }
    return result;
  }

  public static class CustomObjectInputStream extends ObjectInputStream {

    public CustomObjectInputStream(InputStream in) throws IOException {
      super(in);
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
      return Resources.classForName(desc.getName());
    }

  }

}

它是一個序列化緩存裝飾器,用于在保存數據時,把數據序列化成 byte[] 數組,然后把 byte[] 數組保存到委派的緩存實例中去,在查詢數據時,再把查詢出來的數據反序列化為對應的對象。這里要求保存的數據類要實現 Serializable 接口。

2.2.2.6 LoggingCache 緩存裝飾器

接著看 LoggingCache 類型:

public class LoggingCache implements Cache {

  private final Log log;
  private final Cache delegate;
  protected int requests = 0;
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

}

這個緩存裝飾器的功能就是在查詢緩存的時候打印日志,會根據緩存的請求次數與實際命中的次數計算出的命中率,并且打印出來。

2.2.2.7 SynchronizedCache 緩存裝飾器

接著看 SynchronizedCache 類:

public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

}

它是一個實現同步功能的緩存裝飾器,在調用查詢緩存、保存緩存、刪除緩存、清空緩存方法時進行同步,防止多線程同時操作。

2.2.2.8 BlockingCache 緩存裝飾器

我們看最后一個緩存裝飾器 BlockingCache:

/**
 * 一個簡單的阻塞裝飾器。
 * 一個簡單的低效的 EhCache's BlockingCache 裝飾器。當元素不存在緩存中的時候,它設置一個鎖。
 * 這樣其他線程將會等待,直到元素被填充,而不是直接訪問數據庫。
 * 本質上,如果使用不當,它將會造成死鎖。
 *
 * <p>Simple blocking decorator
 *
 * <p>Simple and inefficient version of EhCache's BlockingCache decorator.
 * It sets a lock over a cache key when the element is not found in cache.
 * This way, other threads will wait until this element is filled instead of hitting the database.
 *
 * <p>By its nature, this implementation can cause deadlock when used incorrecly.
 *
 * @author Eduardo Macarron
 *
 */
public class BlockingCache implements Cache {

  private long timeout;
  private final Cache delegate;
  private final ConcurrentHashMap<Object, CountDownLatch> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    // 獲取鎖
    acquireLock(key);
    // 獲取對象
    Object value = delegate.getObject(key);
    if (value != null) {
      // 獲取的數據不為空,釋放鎖
      releaseLock(key);
    }
    // 如果 value 為空,則一直不釋放鎖,讓其他查詢此 key 的線程永久阻塞,直到該 key 對應的 value 被添加到緩存中,或者調用刪除 key 操作,才會釋放鎖。
    // 這樣的操作是用于解決緩存穿透問題,防止大量請求訪問一個目前不存在的數據
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  private void acquireLock(Object key) {
    // 創建一個倒計時閉鎖
    CountDownLatch newLatch = new CountDownLatch(1);
    while (true) {
      // 根據給定的 key,放入對應的閉鎖
      // 如果 key 對應的閉鎖不存在,則放入閉鎖,如果存在則不放入,返回以前的值
      CountDownLatch latch = locks.putIfAbsent(key, newLatch);
      if (latch == null) {
        // latch 為 null 說明放入成功,則退出
        break;
      }
      // latch 不為空,說已經有線程放入了 key 對應的閉鎖,那就讓閉鎖阻塞 await,直到閉鎖被放入它的線程解鎖
      try {
        if (timeout > 0) {
          boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
          if (!acquired) {
            throw new CacheException(
                "Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
          }
        } else {
          latch.await();
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    }
  }

  /**
   * 釋放鎖,它會在保存對象、查詢到對象、移除對象時進行調用
   *
   * @param key
   */
  private void releaseLock(Object key) {
    // 釋放一個鎖
    CountDownLatch latch = locks.remove(key);
    if (latch == null) {
      throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
    }
    // 倒計時
    latch.countDown();
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }
}

這個類是借助了 CountDownLatch 閉鎖實現了先阻塞操作。當一個線程嘗試獲取緩存數據時,會創建一個 CountDownLatch,然后再去獲取數據,當獲取的數據不為空,就把這個 CountDownLatch 刪除,否則不刪除閉鎖,返回空數據。

這樣其他線程獲取相同 key 對應的緩存時,會拿到這個 CountDownLatch,然后調用它的 await() 方法,該線程就會被阻塞起來,直到這個 CountDownLatch 執行了 countDown() 方法。

當 key 對應的數據被獲取到、被刪除、被重新填入時,會調用到 CountDownLatch 的 countDown() 方法,喚醒其他被該閉鎖阻塞的線程。

這樣做的目的是為了防止緩存擊穿。在一個 session 當訪問一個數據庫中一直不存在的數據時,會觸發一次數據庫查詢,此時當 session 還沒有提交事務時,此時出現了大量的 session 也是查詢該 key 對應的數據,這樣就會導致它們都會查詢數據庫,可想而知,后來這些 session 的查詢數據庫行為是無效的,而且如果此時 session 過多,可能會打死數據庫。

為了避免這樣的情況,為一個 key 增加一個閉鎖,阻塞那些獲取該數據的線程,直到數據被填充或釋放鎖才能被喚醒。

這樣的做是比較低效的,容易引發死鎖,比如一個線程如果一直訪問緩存中不存在,并且數據庫中也不存在的數據時,會創建一個閉鎖,查詢數據結束也不會釋放鎖。其他獲取該 key 數據的線程訪問時將會永久的阻塞,嚴重的消耗的系統資源。

這個類一般是不用的,cache 元素中的 block 屬性默認是 false。

2.2.2.9 緩存小結

上述就是緩存裝飾器的全部的介紹了,上面的這些緩存裝飾器是使用了適配器模式,如下圖:

mybatis核心流程的示例分析

這樣設計的好處是,根據各個功能設計出各個裝飾器,讓它們各司其職。

2.2.3 buildStatementFromContext() 構建 SQLStatement 流程

接著看構建 SQLStatement 邏輯,它通過調用 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法來執行。

org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)

 /**
   * 從上下文構建狀態
   *
   * @param list
   */
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 遍歷所有的 select、insert、update、delete 的語句
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析 SQL 語句
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // 添加不完整的聲明
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

可以看到,這里獲取了 select|insert|update|delete 這些元素,然后遍歷,通過創建一個 XMLStatementBuilder 類,調用了它的 parseStatementNode() 方法來進行解析,說明一個 select|insert|update|delete 語句對應著一個 XMLStatement,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode:

  /**
   * 解析增刪改查 SQL 語句聲明,一個增刪改查 SQL 語句就對應一個 MappedStatement
   */
  public void parseStatementNode() {
    // SQL 的 ID 屬性
    String id = context.getStringAttribute("id");
    // 數據庫 ID
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 節點名稱
    String nodeName = context.getNode().getNodeName();
    // 根據節點名稱解析 SQL 的類型:增刪改查
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否為查詢類型
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 是否刷新緩存,除了 select 類型的 SQL 預計,執行的時候都會刷新緩存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 是否使用緩存,默認不填寫時是使用緩存的,如果是 select 類型,則默認是啟用緩存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 結果排序,false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 解析 includes
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析參數類型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 解析語言驅動
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 解析查詢尋的 key
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 解析 selectKey
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    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))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 創建數據源
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // 聲明類型,默認是 PREPARED 類型,預裝配模式
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // fetchSize
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 超時屬性
    Integer timeout = context.getIntAttribute("timeout");
    // 參數映射
    String parameterMap = context.getStringAttribute("parameterMap");
    // 結果類型
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // 結果映射
    String resultMap = context.getStringAttribute("resultMap");
    // 結果集類型
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    // key 屬性
    String keyProperty = context.getStringAttribute("keyProperty");
    // key 列
    String keyColumn = context.getStringAttribute("keyColumn");
    // 結果集
    String resultSets = context.getStringAttribute("resultSets");

    // 構建映射聲明對象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

可以看到它的邏輯:

  1. 獲取元素的 id 屬性、 databaseId 屬性;

  2. 根據節點名稱解析 SQL 命令類型(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH);

  3. 獲取元素的是否查詢類型 isSelect、是否刷新緩存 isSelect、是否使用緩存 isSelect、是否對結果排序 resultOrdered;

  4. 解析 include 元素節點;

  5. 解析元素的 parameterType 屬性、解析語言驅動 lang 屬性、解析 selectKey;

  6. 創建 keyGenerator;

  7. 創建數據源 sqlSource;

  8. 解析 StatementType 類型,默認是 PREPARED 類型;

  9. 獲取 fetchSize、timeout 超時屬性、parameterMap 參數映射、resultType 結果類型、resultMap 結果集、resultSetType 結果集類型、

  10. 獲取元素的 keyProperty 屬性、keyColumn、resultSets

  11. 通過 MapperBuilderAssistant 映射構建器輔助器調用 addMappedStatement() 方法,創建并添加映射 Statement。

我們看下 org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement() 方法:

  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    // 解析聲明 ID
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // 開始構建一個映射聲明
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    // 獲取聲明參數映射
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    // 把聲明對象放入 configuration 中
    configuration.addMappedStatement(statement);
    return statement;
  }

  public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) {
      return null;
    }
    if (isReference) {
      // is it qualified with any namespace yet?
      if (base.contains(".")) {
        return base;
      }
    } else {
      // is it qualified with this namespace yet?
      if (base.startsWith(currentNamespace + ".")) {
        return base;
      }
      if (base.contains(".")) {
        throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
      }
    }
    // 格式為:命令空間 + "." + base
    return currentNamespace + "." + base;
  }

這里的邏輯:

  1. 根據命令空間以及元素 ID 生成一個 MappedStatement 的 ID 屬性;

  2. 創建一個 MappedStatement.Builder 實例構建 MappedStatement 實例;

  3. 添加到 configuration 實例中,返回 MappedStatement 實例。

這個 MappedStatement 的生命周期是和 configuration 一樣,也是和應用程序的生命周期一樣。

2.2.4 bindMapperForNamespace() 從命令空間構建映射

這個方法是根據 mapper.xml 中的命名空間來注冊對應的 Mapper 接口類,org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace:

private void bindMapperForNamespace() {
    // 當前命令空間
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 綁定類型就是命名空間對應的接口類
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        // 保存命令空間
        configuration.addLoadedResource("namespace:" + namespace);
        // 保存映射,這里進行了注冊
        configuration.addMapper(boundType);
      }
    }
  }

邏輯:

  1. 首先獲取了命令空間值,然后加載這個類型,得到的就是對應的聲明的 Mapper 接口;

  2. 保存命令空間到 Configuration 配置中;

  3. 把 Mapper 接口注冊到 Configuration 中。

我們再看下 configuration.addMapper(boundType); 這個邏輯,org.apache.ibatis.session.Configuration#addMapper:

// org.apache.ibatis.session.Configuration#addMapper:
public <T> void addMapper(Class<T> type) {
    // mapperRegistry 是 MapperRegistry 類型
    mapperRegistry.addMapper(type);
  }

里邊又調用了 org.apache.ibatis.binding.MapperRegistry#addMapper() 方法:

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 添加一個映射器代理工廠
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 映射注解構建器
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

我們看到了這里的邏輯:

  1. 把要注冊的類保存到 Map<Class<?>, MapperProxyFactory<?>> 類型的 knownMappers 屬性中,它的 key 為注冊的類型,value 為 MapperProxyFactory 映射代理工廠類型實例;

  2. 創建一個 MapperAnnotationBuilder 映射注解解析器,對目標類型進行解析。

2.2.4.1 MapperProxyFactory 映射代理工廠

我們看下這個類:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
  1. 這個類中維護目標接口類型信息、方法與映射方法執行器屬性。

  2. 它提供了創建實例方法 newInstance(),通過 JDK 的動態代理對象創建一個目標接口的代理對象。

2.2.4.2 MapperProxy 映射代理

上面 JDK 動態代理對象時候,傳入了一個 MapperProxy 類型的參數,它的實現為:

/**
 * 方法代理器,實現了 JDK 動態代理的執行處理器 InvocationHandler 接口
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  static {
    Method privateLookupIn;
    try {
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
      // JDK 1.8
      try {
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Exception e) {
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  /**
   * 動態代理執行器的 invoke 方法
   *
   * @param proxy
   * @param method
   * @param args
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 調用 MapperMethodInvoker 映射方法執行器
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929
      // 從方法緩存中獲取映射方法執行器
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
        return invoker;
      }

      // 創建一個新的方法執行器,并放入 methodCache 緩存中
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          // 如果方法是一個接口的 default 方法,那就創建一個 DefaultMethodInvoker 類型
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          // 否則就創建普通的 PlainMethodInvoker 類型執行器
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

  private MethodHandle getMethodHandleJava9(Method method)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
        declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
        declaringClass);
  }

  private MethodHandle getMethodHandleJava8(Method method)
      throws IllegalAccessException, InstantiationException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  }

  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    /**
     * JDK 動態代理對象的的處理器方法
     *
     * @param proxy
     * @param method
     * @param args
     * @param sqlSession
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // 執行目標方法
      return mapperMethod.execute(sqlSession, args);
    }
  }

  private static class DefaultMethodInvoker implements MapperMethodInvoker {
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
      super();
      this.methodHandle = methodHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // 通過 MethodHandle 方法處理器,綁定代理對象,執行方法
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
  }

再看下它的類圖:

mybatis核心流程的示例分析

它實現了 InvocationHandler 接口的 invoke() 方法,里邊主要的邏輯是:

  1. 調用 cachedInvoker() 方法,創建一個 MapperMethodInvoker;

    1. 先從 methodCache 緩存中獲取,有的話直接返回;

    2. methodCache 緩存沒有的話,則創建一個 PlainMethodInvoker 類型的執行器,這個構造器會被傳入一個 org.apache.ibatis.binding.MapperMethod 類型對象。

  2. 調用 MapperMethodInvoker 實例的 invoke() 執行目標方法,實際最終會執行 MapperMethod 實例的 execute() 方法。

2.2.4.3 MapperMethod 映射方法

我們看下 MapperMethod 類:

/**
 * 映射方法
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 * @author Lasse Voss
 * @author Kazuki Shimizu
 */
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // SQL 命令
    this.command = new SqlCommand(config, mapperInterface, method);
    // 方法簽名
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  /**
   * 執行方法
   *
   * @param sqlSession
   * @param args
   * @return
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        // 新增類型
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        // 修改
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        // 刪除
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        // 查詢
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          // 返回多條
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          // 返回 map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          // 返回游標
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        // 刷新
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (!StatementType.CALLABLE.equals(ms.getStatementType())
        && void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName()
          + " needs either a @ResultMap annotation, a @ResultType annotation,"
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }

  /**
   * 查詢多條記錄
   *
   * @param sqlSession
   * @param args
   * @param <E>
   * @return
   */
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 轉換參數
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      // 有行綁定
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

  private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
    Cursor<T> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectCursor(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectCursor(command.getName(), param);
    }
    return result;
  }

  private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
  }
  
  // ...省略無關方法...

重點看下它 execute() 方法邏輯:

  1. 判斷 SQL 執行類型:insert、update、delete、select;

  2. 根據執行類型最終都會調用 SqlSession 的對應方法,而 SqlSession 的對應方法內部最終會調用 Executor 的對應方法。

2.3 創建 SqlSession 流程

上面我們講了解析 mybatis-config.xml 以及 mapper.xml 的流程,現在我們來看下獲取一個 SqlSession 的流程。

從 1. 例子的單元測類中可以看到,它是通過 SqlSession sqlSession = sqlSessionFactory.openSession() 來獲取一個 SqlSession,sqlSessionFactory.openSession 是 DefaultSqlSessionFactory 類型的,我們看下它的 openSession() 方法,org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession():

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  /**
   * 打開一個 session
   *
   * @param execType
   * @param level
   * @param autoCommit
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 獲取環境信息
      final Environment environment = configuration.getEnvironment();
      // 獲取事務工廠
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 獲取一個事務
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 創建一個執行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 創建一個默認的 DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      // 遇到異常關閉事務
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 /**
   * 從環境信息中創建一個事務工廠
   *
   * @param environment
   * @return
   */
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      // 創建默認的管理的事務工廠
      return new ManagedTransactionFactory();
    }
    // 從環境中獲取事務工廠
    return environment.getTransactionFactory();
  }

  /**
   * 關閉事務
   *
   * @param tx
   */
  private void closeTransaction(Transaction tx) {
    if (tx != null) {
      try {
        tx.close();
      } catch (SQLException ignore) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

可以看到,它的主要流程為:

  1. 獲取環境 Environment 信息;

  2. 獲取一個 TransactionFactory 事務工廠實例;

  3. 通過事務工廠創建一個事務 Transaction 實例;

  4. 通過配置類創建一個 Executor 執行器;

  5. 創建一個 DefaultSqlSession 對象返回;

  6. 遇到異常關閉事務。

2.3.1 獲取事務工廠 TransactionFactory

因為我們在 mybatis-config.xml 中配置了環境信息 environment,其中 transactionManager 元素的 type 為 JDBC ,所以 它會獲取到的事務工廠為 JdbcTransactionFactory 類型。

然后通過它來創建了一個事務,org.apache.ibatis.transaction.TransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean):

/**
 * Creates {@link JdbcTransaction} instances.
 *
 * @author Clinton Begin
 *
 * @see JdbcTransaction
 */
public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

2.3.2 獲取事務 Transaction

我們看下 newTransaction() 方法返回的 JdbcTransaction 類型:

mybatis核心流程的示例分析

它的實現:

/**
 * {@link Transaction} that makes use of the JDBC commit and rollback facilities directly.
 * It relies on the connection retrieved from the dataSource to manage the scope of the transaction.
 * Delays connection retrieval until getConnection() is called.
 * Ignores commit or rollback requests when autocommit is on.
 *
 * @author Clinton Begin
 *
 * @see JdbcTransactionFactory
 */
public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }

  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }

  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }

  protected void resetAutoCommit() {
    try {
      if (!connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Error resetting autocommit to true "
            + "before closing the connection.  Cause: " + e);
      }
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}

這是一個jdbc 事務,里邊提供了一些獲取數據庫連接、提交事務、回滾、關閉事務操作。

2.3.3 創建執行器 Executor

接著通過 configuration 創建一個執行器 Executor,org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType):

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      // 批量執行器
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      // 重用執行器
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 簡單執行器
      executor = new SimpleExecutor(this, transaction);
    }
    // 如果啟用二級緩存
    if (cacheEnabled) {
      // 創建一個 CachingExecutor 類型,使用裝飾器模式
      executor = new CachingExecutor(executor);
    }
    // 添加攔截器,這里用戶可以實現自定義的攔截器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

這里的邏輯:

  1. 判斷參數 ExecutorType 的類型,根據它的類型來創建不同的執行器,默認是 SIMPLE 類型;

  2. ExecutorType.BATCH 類型,則創建 BatchExecutor 執行器;

  3. ExecutorType.REUSE 類型,則創建 ReuseExecutor 執行器;

  4. 否則創建 SimpleExecutor 執行器;

  5. 如果啟用了二級緩存,則創建 CachingExecutor 緩存執行器來包裝上述執行器。默認是啟用二級緩存;

  6. 為添加攔截器,這里用戶可以實現自定義的攔截器;

  7. 返回執行器。

我們看下執行器 Executor 的類圖:

mybatis核心流程的示例分析

可以看到,Executor 的繼承類圖,CachingExecutor 是一個裝飾器,里邊維護了一個真正的執行器,它默認實現的 SimpleExecutor 類型。

2.3.3.1 BaseExecutor 執行器

我們先看下 BaseExecutor 類的實現如下:

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  protected Transaction transaction;
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

  /**
   * 本地緩存,一級緩存
   */
  protected PerpetualCache localCache;

  /**
   * 本地輸出參數緩存
   */
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    // 這是干啥的?
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    // 本地
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore. There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

  @Override
  public boolean isClosed() {
    return closed;
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 執行刷新聲明
    return doFlushStatements(isRollBack);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 綁定一個 SQL
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 構建一個一級緩存 key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 清除本地緩存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 從一級緩存中獲取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 查詢數據庫
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      // TODO: 2020/9/18 引用隊列?
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {
      deferredLoad.load();
    } else {
      // 這是干甚的?
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }

  /**
   * 創建二級緩存 key
   *
   * @param ms
   * @param parameterObject
   * @param rowBounds
   * @param boundSql
   * @return
   */
  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return localCache.getObject(key) != null;
  }

  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清除本地緩存
    clearLocalCache();
    // 刷新聲明
    flushStatements();
    if (required) {
      // 事務提交
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

  protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }

  /**
   * Apply a transaction timeout.
   *
   * @param statement
   *          a current statement
   * @throws SQLException
   *           if a database access error occurs, this method is called on a closed <code>Statement</code>
   * @since 3.4.0
   * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
   */
  protected void applyTransactionTimeout(Statement statement) throws SQLException {
    StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  }

  /**
   * 處理本地緩存輸出參數
   *
   * @param ms
   * @param key
   * @param parameter
   * @param boundSql
   */
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    // 處理 callable 類型,存儲過程、存儲函數
    if (ms.getStatementType() == StatementType.CALLABLE) {
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

  /**
   * 從數據庫獲取
   *
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param key
   * @param boundSql
   * @param <E>
   * @return
   * @throws SQLException
   */
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 放入占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 開始真正的查詢數據
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 清除本地緩存
      localCache.removeObject(key);
    }
    // 放入本地緩存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      // 如果是調用存儲過程、存儲函數,則把參數放入緩存中
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

  @Override
  public void setExecutorWrapper(Executor wrapper) {
    this.wrapper = wrapper;
  }

  private static class DeferredLoad {

    private final MetaObject resultObject;
    private final String property;
    private final Class<?> targetType;
    private final CacheKey key;
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    private final ResultExtractor resultExtractor;

    // issue #781
    public DeferredLoad(MetaObject resultObject,
                        String property,
                        CacheKey key,
                        PerpetualCache localCache,
                        Configuration configuration,
                        Class<?> targetType) {
      this.resultObject = resultObject;
      this.property = property;
      this.key = key;
      this.localCache = localCache;
      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }

    public boolean canLoad() {
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    public void load() {
      @SuppressWarnings("unchecked")
      // we suppose we get back a List
      List<Object> list = (List<Object>) localCache.getObject(key);
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

  }

}

這個類是抽象類,它實現了 Executor 接口的核心方法,留下一些抽象方法和模板方法交給了子類實現。這個類主要提供幾個主要的屬性:

  1. PerpetualCache 類型的 localCache 屬性,這是一個一級緩存,在同一個 sqlSession 查詢相同接口數據時,提供緩存數據,避免查詢相同查詢語句和參數再次查詢數據庫。在查詢時會從緩存中查找,以及保存緩存,在更新、刪除都會清空緩存;

  2. 持有事務 Transaction 屬性,用于在執行完一些事務提交、回滾、操作操作時,委派事務執行對應的邏輯;

2.3.3.2 SimpleExecutor 執行器

默認的實際執行器是 SimpleExecutor 類型,看下它的實現:

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      // 獲取配置類型
      Configuration configuration = ms.getConfiguration();
      // 獲取 StatementHandler 處理器
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 創建 Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 獲取配置類型
      Configuration configuration = ms.getConfiguration();
      // 獲取 StatementHandler 處理器
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 創建 Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    // 獲取配置類型
    Configuration configuration = ms.getConfiguration();
    // 獲取 StatementHandler 處理器
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    // 創建 Statement
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    return Collections.emptyList();
  }

  /**
   * 準備一個 Statement
   *
   * @param handler
   * @param statementLog
   * @return
   * @throws SQLException
   */
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 獲取連接
    Connection connection = getConnection(statementLog);
    // 通過 StatementHandler 創建一個 Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 初始化參數
    handler.parameterize(stmt);
    return stmt;
  }

}

這個類主要實現了 BaseExecutor 抽象的類的抽象的模板方法:doUpdate()、doQuery()、doQueryCursor()、doFlushStatements() 方法,這些方法主要的邏輯為:

  1. 獲取 Configuration 配置類;

  2. 通過配置類 Configuration 的 newStatementHandler() 方法來創建 StatementHandler 類;

  3. 調用 prepareStatement() 方法,通過 StatementHandler 創建 Statement;

  4. 再通過 StatementHandler 執行對應的查詢、更新相關方法。

2.3.3.2.1 StatementHandler 處理器

在上述的 SimpleExecutor 類中,通過配置類 Configuration 的 newStatementHandler() 方法獲取 StatementHandler 實例,我們先看下 StatementHandler 的類圖:

mybatis核心流程的示例分析

我們看下它的實現,org.apache.ibatis.session.Configuration#newStatementHandler:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 創建一個 RoutingStatementHandler 路由的聲明處理器
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 對 StatementHandler 應用插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

它的邏輯:

  1. 創建一個 RoutingStatementHandler 路由的聲明處理器;

  2. 對 StatementHandler 應用插件;

  3. 返回 statementHandler。

繼續看下 RoutingStatementHandler 這個類:

public class RoutingStatementHandler implements StatementHandler {

  /**
   * 關聯一個真正的 RoutingStatementHandler
   */
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    delegate.batch(statement);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    return delegate.queryCursor(statement);
  }

  @Override
  public BoundSql getBoundSql() {
    return delegate.getBoundSql();
  }

  @Override
  public ParameterHandler getParameterHandler() {
    return delegate.getParameterHandler();
  }
}

可以看到,這個類實現了 StatementHandler 接口,并且根據 MappedStatement 獲取 StatementType,創建對應的 StatementHandler,有:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。默認是會創建 PreparedStatementHandler 實例。

它的其他方法都是使用委派的 StatementHandler 實例去執行,比如 prepare()、parameterize()、batch()、update()、query()、queryCursor()、getBoundSql()、getParameterHandler() 方法。

2.3.3.2.2 PreparedStatementHandler

我們看下實際的 PreparedStatementHandler 類:

public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執行更新
    ps.execute();
    // 獲取更新的行數
    int rows = ps.getUpdateCount();
    // 獲取參數對象
    Object parameterObject = boundSql.getParameterObject();
    // 獲取鍵生成器
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    // 后置處理器鍵,比如這里會針對 insert 語句,會設置插入之后的主鍵到參數對象上。
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 批量查詢
    ps.addBatch();
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執行查詢
    ps.execute();
    // 通過結果集處理器處理結果
    return resultSetHandler.handleResultSets(ps);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執行查詢
    ps.execute();
    // 結果集處理器處理數據
    return resultSetHandler.handleCursorResultSets(ps);
  }

  /**
   * 初始化一個 Statement
   *
   * @param connection
   * @return
   * @throws SQLException
   */
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      //
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // 使用參數化對象進行設置參數
    parameterHandler.setParameters((PreparedStatement) statement);
  }

}

這個類就是實際真正執行目標 SQL 邏輯的類,它的一些方法邏輯:

  1. update() 方法中,會通過 PreparedStatement 執行 SQL,然后獲取參數對象、鍵生成器,對參數進行后置處理;

  2. query()、queryCursor() 方法中,會通過 PreparedStatement 執行 SQL,然后通過結果集處理器對結果進行處理;

2.3.4 CachingExecutor 緩存執行器

接著該看 CachingExecutor 類了:

/**
 * 緩存執行器,裝飾器模式,聲明周期是一個 session
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class CachingExecutor implements Executor {

  /**
   * 委派的執行器
   */
  private final Executor delegate;

  /**
   * 事務緩存管理器
   */
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

  @Override
  public Transaction getTransaction() {
    return delegate.getTransaction();
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      // issues #499, #524 and #573
      if (forceRollback) {
        tcm.rollback();
      } else {
        tcm.commit();
      }
    } finally {
      delegate.close(forceRollback);
    }
  }

  @Override
  public boolean isClosed() {
    return delegate.isClosed();
  }

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.queryCursor(ms, parameter, rowBounds);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 綁定 SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 構建緩存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 獲取二級緩存配置,它是從解析 mapper.xml 和 mapper 接口的 @CacheNamespace 注解得出來的
    Cache cache = ms.getCache();
    if (cache != null) {
      // 是否需要刷新緩存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 緩存管理器,把緩存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 委派實際的 BaseExecutor 類型的查詢
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return delegate.flushStatements();
  }

  @Override
  public void commit(boolean required) throws SQLException {
    // 提交事務
    delegate.commit(required);
    // 事務緩存管理器提交
    tcm.commit();
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      delegate.rollback(required);
    } finally {
      if (required) {
        tcm.rollback();
      }
    }
  }

  private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
        if (parameterMapping.getMode() != ParameterMode.IN) {
          throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
        }
      }
    }
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return delegate.isCached(ms, key);
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    delegate.deferLoad(ms, resultObject, property, key, targetType);
  }

  @Override
  public void clearLocalCache() {
    delegate.clearLocalCache();
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      // 查詢之前,先清空二級緩存
      tcm.clear(cache);
    }
  }

  @Override
  public void setExecutorWrapper(Executor executor) {
    throw new UnsupportedOperationException("This method should not be called");
  }

}

這個類是一個 Executor 的裝飾器類,主要提供了二級緩存功能。它在查詢數據、更新數據、提交、回滾操作時,會對二級緩存進行處理。

它的查詢數據邏輯:

  1. 構建一個 CacheKey 類型的緩存 key;

  2. 從 MappedStatement 中獲取二級緩存 Cache;

  3. 如果 cache 為空,則執行實際的委派執行器執行查詢數據;

  4. 如果 cache 不為空,則先判斷是否需要刷新緩存,如果需要刷新則通過 TransactionalCacheManager 清除緩存;然后從 TransactionalCacheManager 對象中獲取 key 對應的二級緩存數據,緩存數據不為空直接返回,否則就繼續執行實際委派執行器查詢數據,然后把數據緩存到二級緩存中。

  5. 最后返回數據。

2.3.4.1 TransactionalCacheManager 事務緩存管理器

看下 TransactionalCacheManager 的實現:

public class TransactionalCacheManager {

  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  public void putObject(Cache cache, CacheKey key, Object value) {
    // 獲取 cache 對應的 TransactionalCache,然后把 key 和 value 存入
    getTransactionalCache(cache).putObject(key, value);
  }

  public void commit() {
    // 遍歷事務緩存
    for (TransactionalCache txCache : transactionalCaches.values()) {
      // 提交事務
      txCache.commit();
    }
  }

  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    // 如果 transactionalCaches 中的 cache 鍵沒有對應的數據,則創建 TransactionalCache 對象
    // 把 cache 對象當做 TransactionalCache 構造器的參數傳入
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

這個類持有一個 key 是 Cache 類型,value 為 TransactionalCache 類型的 HashMap 類型屬性 transactionalCaches,來保存事務緩存數據。

它的 getTransactionalCache() 方法中,參數 cache 是外部傳入的二級緩存,當 transactionalCaches 沒有這個 cache 對應的 value 時,就創建一個 TransactionalCache 類,并且把 cache 作為參數傳入它的構造器中,保存起來。

它的結構為:

mybatis核心流程的示例分析

TransactionalCacheManager 這個個在保存緩存數據時,會調用 TransactionalCache 的 putObject() 方法,在提交事務、回滾事務的時候,會調用 TransactionalCache 的 commit() 和 rollback() 方法。

2.3.4.2 TransactionalCache 事務緩存

我們詳細看下這個類。還記得上面 2.2.2 中我們講過的緩存裝飾器嗎?沒錯這里又看見了一個緩存裝飾器 TransactionalCache,它是實現如下:

/**
 * The 2nd level cache transactional buffer.
 * <p>
 * This class holds all cache entries that are to be added to the 2nd level cache during a Session.
 * Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
 * Blocking cache support has been added. Therefore any get() that returns a cache miss
 * will be followed by a put() so any lock associated with the key can be released.
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;

  /**
   * 事務未提交前的保存的緩存數據
   */
  private final Map<Object, Object> entriesToAddOnCommit;

  /**
   * 事務未提交前未命中的緩存數據
   */
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public void putObject(Object key, Object object) {
    // 把數據先臨時保存起來
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      // 提交的時候清理二級緩存
      delegate.clear();
    }
    // 提交的時候,刷新查詢的數據,用于保存到二級緩存中
    flushPendingEntries();
    reset();
  }

  public void rollback() {
    // 回滾時解析未命中的數據
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  private void flushPendingEntries() {
    // 提交的時候,把臨時保存的數據,真正放入二級緩存中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    // 移除未命中的數據
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter. "
            + "Consider upgrading your cache adapter to the latest version. Cause: " + e);
      }
    }
  }

}

這個類它也是有持有一個實際的委派的緩存,它默認是我們在 2.2.2 節中講到的 SynchronizedCache 裝飾過的二級緩存。

這個類還有個兩個屬性:Map<Object, Object> entriesToAddOnCommit 和 Set<Object> entriesMissedInCache,它們的作用是在 session 事務沒有提交之前,臨時保存緩存數據,等待真正的事務提交 commit() 時才會把緩存同步到二級緩存中,在回滾 rollback() 等時會清除未命中的緩存。

我們通過在它的 getObject() 方法中打斷點,可以得到如下所示的結論。它是一個緩存裝飾器,一層層的包裝。

mybatis核心流程的示例分析

注意了 TransactionalCache 的聲明周期不與委派的二級緩存一樣,它是和一個 SqlSession 的聲明一樣的。而委派的二級緩存是和應用程序的生命周期一樣的。

2.3.5 應用插件 interceptorChain.pluginAll()

我們再看下為執行器應用插件的邏輯 interceptorChain.pluginAll(executor)

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

這里會遍歷所有的實現了 Interceptor 接口的攔截器類,調用它們的 plugin() 方法,對目標類進行攔截。實際上攔截器的調用一共有四個地方:

mybatis核心流程的示例分析

分別是:

  1. 創建 ParameterHandler 參數處理器時的攔截;

  2. 創建 ResultSetHandler 結果集處理器的攔截;

  3. 創建 StatementHandler 的攔截;

  4. 創建 Executor 的攔截。

我們可以實現自己的攔截器,根據自己的需求針對這四種類型進行攔截調用。比如可以針對 ParameterHandler 類型進行攔截,實現自動查詢增加分頁 SQL 的功能等等。

2.3.5 創建 DefaultSqlSession

最后一步是根據已經創建好的 Executor 和 Configuration 來創建一個 DefaultSqlSession 實例。

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }

  @Override
  public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
  }

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
    return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
            configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement) {
    return selectCursor(statement, null);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter) {
    return selectCursor(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 從配置類中獲取映射聲明對象
      // MappedStatement 聲明周期很長,隨著容器的關閉而關閉
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 查詢數據
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void select(String statement, Object parameter, ResultHandler handler) {
    select(statement, parameter, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, ResultHandler handler) {
    select(statement, null, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public int insert(String statement) {
    return insert(statement, null);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public int delete(String statement) {
    return update(statement, null);
  }

  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void rollback() {
    rollback(false);
  }

  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public List<BatchResult> flushStatements() {
    try {
      return executor.flushStatements();
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

  private void closeCursors() {
    if (cursorList != null && !cursorList.isEmpty()) {
      for (Cursor<?> cursor : cursorList) {
        try {
          cursor.close();
        } catch (IOException e) {
          throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);
        }
      }
      cursorList.clear();
    }
  }

  @Override
  public Configuration getConfiguration() {
    return configuration;
  }

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

  @Override
  public Connection getConnection() {
    try {
      return executor.getTransaction().getConnection();
    } catch (SQLException e) {
      throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
    }
  }

  @Override
  public void clearCache() {
    executor.clearLocalCache();
  }

  private <T> void registerCursor(Cursor<T> cursor) {
    if (cursorList == null) {
      cursorList = new ArrayList<>();
    }
    cursorList.add(cursor);
  }

  private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }

  private Object wrapCollection(final Object object) {
    return ParamNameResolver.wrapToMapIfCollection(object, null);
  }

  /**
   * @deprecated Since 3.5.5
   */
  @Deprecated
  public static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -5741767162221585340L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
      }
      return super.get(key);
    }

  }

}

這個類實現了 SqlSession 接口的增刪改查方法,最終還是委派 Executor 去執行。

2.4 通過 SqlSession 獲取映射接口執行目標方法

接下來,該看通過創建好的 SqlSession 來獲取映射接口執行目標方法的流程了。

// 通過 SqlSession 獲取映射接口
AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
// 執行目標方法  
PrimitiveSubject ps1 = mapper.selectOneById(999);

從上面的分析,我們知道了 sqlSession 是 DefaultSqlSession 類型,它的 getMapper() 方法,我們在 2.3.5 中看到了它的實現,org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper:

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

它會通過配置類 Configuration 根據類型獲取對應的 Mapper 類型,org.apache.ibatis.session.Configuration#getMapper:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

最后再調用 MapperRegistry 實例的 getMapper() 方法,org.apache.ibatis.binding.MapperRegistry#getMapper:

  /**
   * 獲取映射器
   *
   * @param type
   * @param sqlSession
   * @param <T>
   * @return
   */
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 映射器代理工廠獲取代理對象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

看到這里,我們就就比較熟悉了,在 2.2.4 節中講了解析 mapper.xml 文件時,會根據 xml 中的命名空間來注冊對應的 mapper 接口,會以一個 key 為目標接口類型,value 為 MapperProxyFactory 實例的形式保存到一個 HashMap 實例中。

這里就是獲取除了目標類型對應的 MapperProxyFactory 類型,然后調用它的 newInstance() 方法,通過 JDK 動態代理創建代理實例類。

最后,用這個代理對象來執行目標方法。

2.4.1 查詢非緩存數據流程

我們在 org.apache.ibatis.executor.statement.PreparedStatementHandler#query 方法處,打個端點看下它的方法調用棧信息:

// 調用 PreparedStatementHandler 的 query 方法
query(Statement, ResultHandler):71, PreparedStatementHandler (org.apache.ibatis.executor.statement), PreparedStatementHandler.java

// 調用 RoutingStatementHandler 的 query 方法
query(Statement, ResultHandler):82, RoutingStatementHandler (org.apache.ibatis.executor.statement), RoutingStatementHandler.java

// 調用 SimpleExecutor 的 doQuery() 方法
doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql):69, SimpleExecutor (org.apache.ibatis.executor), SimpleExecutor.java

// 調用 BaseExecutor 的 queryFromDatabase() 方法
queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):381, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java

// 調用 CachingExecutor 的 query() 方法
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):173, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):116, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java

// 調用 DefaultSqlSession 的 select() 方法
selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java

// 調用 MapperMethod 類的 execute() 方法
execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java

// 調用 MapperProxy 類的 invoke() 方法
invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java
invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java

// 調用 JDK 動態代理類的 selectOneById() 方法
selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source

// 單元測試類的查詢方法
testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java
...省略無關棧信息...

它的時序圖:

mybatis核心流程的示例分析

sequenceDiagram
# 單元測試入口
AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 單元測試方法
AutoConstructorTest->>$Proxy15:selectOneById()
# JDK代理對象
$Proxy15->>MapperProxy:invoke() 執行
# 代理查詢
MapperProxy->>PlainMethodInvoker:invoke()
PlainMethodInvoker->>MapperMethod:execute()
# 委派 DefaultSqlSession
MapperMethod->>DefaultSqlSession:selectOne()
DefaultSqlSession->>DefaultSqlSession:selectList()
# 委派 CachingExecutor
DefaultSqlSession->>CachingExecutor:query()
CachingExecutor->>CachingExecutor:query()
# BaseExecutor
CachingExecutor->>BaseExecutor:query()
BaseExecutor->>BaseExecutor:queryFromDatabase()
BaseExecutor->>SimpleExecutor:doQuery()
# RoutingStatementHandler
SimpleExecutor->>RoutingStatementHandler:query()
RoutingStatementHandler->>PreparedStatementHandler:query()

2.4.2 二級緩存調用流程

我們再在查詢二級緩邏輯處打斷點,看下它的調用棧信息:

// 調用 PerpetualCache 的 getObject() 方法
getObject(Object):59, PerpetualCache (org.apache.ibatis.cache.impl), PerpetualCache.java

// 調用 LruCache 的 getObject() 方法
getObject(Object):75, LruCache (org.apache.ibatis.cache.decorators), LruCache.java

// 調用 SerializedCache 的 getObject() 方法
getObject(Object):63, SerializedCache (org.apache.ibatis.cache.decorators), SerializedCache.java

// 調用 LoggingCache 的 getObject() 方法
getObject(Object):55, LoggingCache (org.apache.ibatis.cache.decorators), LoggingCache.java

// 調用 SynchronizedCache 的 getObject() 方法
getObject(Object):48, SynchronizedCache (org.apache.ibatis.cache.decorators), SynchronizedCache.java

// 調用 TransactionalCache 的 getObject() 方法
getObject(Object):75, TransactionalCache (org.apache.ibatis.cache.decorators), TransactionalCache.java

// 調用 TransactionalCacheManager 的 getObject() 方法
getObject(Cache, CacheKey):35, TransactionalCacheManager (org.apache.ibatis.cache), TransactionalCacheManager.java

// 調用 CachingExecutor 的 query() 方法
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):114, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java

// 調用 DefaultSqlSession 的 selectList() 方法
selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java

// 調用 DefaultSqlSession 的 selectOne() 方法
selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java

// 調用 MapperMethod 的 execute() 方法
execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java

// 調用 PlainMethodInvoker 的 invoke() 方法
invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java

// 調用 MapperProxy 的 invoke() 方法
invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java

// 調用代理對象的 selectOneById() 方法
selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source

// 單元測試類的方法
testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java
...省略無關棧信息...

畫出二級緩存調用的時序圖:

mybatis核心流程的示例分析

sequenceDiagram
# 單元測試入口
AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 單元測試方法
AutoConstructorTest->>$Proxy15:selectOneById()

# JDK代理對象
$Proxy15->>MapperProxy:invoke() 執行

# 代理查詢
MapperProxy->>PlainMethodInvoker:invoke()
PlainMethodInvoker->>MapperMethod:execute()

# 委派 DefaultSqlSession
MapperMethod->>DefaultSqlSession:selectOne()
DefaultSqlSession->>DefaultSqlSession:selectList()

# 委派 CachingExecutor
DefaultSqlSession->>CachingExecutor:query()
CachingExecutor->>CachingExecutor:query()

# 事務緩存管理器
CachingExecutor->>TransactionalCacheManager:getObject()
# 事務緩存裝飾器
TransactionalCacheManager->>TransactionalCache:getObject()
# 同步緩存裝飾器
TransactionalCache->>SynchronizedCache:getObject()
# 日志緩存裝飾器
SynchronizedCache->>LoggingCache:getObject()
# 序列化裝飾器
LoggingCache->>SerializedCache:getObject()
# Lru 緩存裝飾器
SerializedCache->>LruCache:getObject()
# 實際的緩存
LruCache->>PerpetualCache:getObject()

這里的調用邏輯中,二級緩存的調用鏈可以配合著 2.2.2.9 的緩存小結圖來閱讀。

關于“mybatis核心流程的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宝坻区| 丹江口市| 观塘区| 利川市| 合肥市| 陈巴尔虎旗| 响水县| 宁强县| 上思县| 锦州市| 三原县| 沂南县| 和政县| 延吉市| 宁陵县| 广西| 淮安市| 象州县| 惠来县| 金川县| 宣化县| 富蕴县| 紫阳县| 夹江县| 宁乡县| 台安县| 巧家县| 泉州市| 兴化市| 札达县| 商水县| 邢台市| 商洛市| 龙泉市| 宣化县| 南陵县| 自治县| 庆安县| 邵东县| 施秉县| 古丈县|