面試官問你MyBatis SQL是如何執行的?把這篇文章甩給他

初識 MyBatis

MyBatis 是第一個支持自定義 SQL、存儲過程和高級映射的類持久框架。MyBatis 消除了大部分 JDBC 的樣板代碼、手動設置參數以及檢索結果。MyBatis 可以支持簡單的 XML 和註解配置規則。使 Map 接口和 POJO 類映射到數據庫字段和記錄。html

MyBatis 的特色

那麼 MyBatis 具備什麼特色呢?或許咱們能夠從以下幾個方面來描述java

  • MyBatis 中的 SQL 語句和主要業務代碼分離,咱們通常會把 MyBatis 中的 SQL 語句統一放在 XML 配置文件中,便於統一維護。

image.png

  • 解除 SQL 與程序代碼的耦合,經過提供 DAO 層,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。SQL 和代碼的分離,提升了可維護性。

image.png

  • MyBatis 比較簡單和輕量

自己就很小且簡單。沒有任何第三方依賴,只要經過配置 jar 包,或者若是你使用 Maven 項目的話只須要配置 Maven 以來就能夠。易於使用,經過文檔和源代碼,能夠比較徹底的掌握它的設計思路和實現。mysql

  • 屏蔽樣板代碼

MyBatis 回屏蔽原始的 JDBC 樣板代碼,讓你把更多的精力專一於 SQL 的書寫和屬性-字段映射上。sql

  • 編寫原生 SQL,支持多表關聯

image.png

MyBatis 最主要的特色就是你能夠手動編寫 SQL 語句,可以支持多表關聯查詢。數據庫

  • 提供映射標籤,支持對象與數據庫的 ORM 字段關係映射
ORM 是什麼? 對象關係映射(Object Relational Mapping,簡稱ORM) ,是經過使用描述對象和數據庫之間映射的元數據,將面嚮對象語言程序中的對象自動持久化到關係數據庫中。本質上就是將數據從一種形式轉換到另一種形式。
  • 提供 XML 標籤,支持編寫動態 SQL。

你可使用 MyBatis XML 標籤,起到 SQL 模版的效果,減小繁雜的 SQL 語句,便於維護。apache

MyBatis 總體架構

MyBatis 最上面是接口層,接口層就是開發人員在 Mapper 或者是 Dao 接口中的接口定義,是查詢、新增、更新仍是刪除操做;中間層是數據處理層,主要是配置 Mapper -> XML 層級之間的參數映射,SQL 解析,SQL 執行,結果映射的過程。上述兩種流程都由基礎支持層來提供功能支撐,基礎支持層包括鏈接管理,事務管理,配置加載,緩存處理等。設計模式

image.png

接口層

在不與Spring 集成的狀況下,使用 MyBatis 執行數據庫的操做主要以下:緩存

InputStream is = Resources.getResourceAsStream("myBatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
sqlSession = factory.openSession();

其中的SqlSessionFactory,SqlSession是 MyBatis 接口的核心類,尤爲是 SqlSession,這個接口是MyBatis 中最重要的接口,這個接口可以讓你執行命令,獲取映射,管理事務。服務器

數據處理層

  • 配置解析

在 Mybatis 初始化過程當中,會加載 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的註解信息,解析後的配置信息會造成相應的對象並保存到 Configration 對象中。以後,根據該對象建立SqlSessionFactory 對象。待 Mybatis 初始化完成後,能夠經過 SqlSessionFactory 建立 SqlSession 對象並開始數據庫操做。session

  • SQL 解析與 scripting 模塊

Mybatis 實現的動態 SQL 語句,幾乎能夠編寫出全部知足須要的 SQL。

Mybatis 中 scripting 模塊會根據用戶傳入的參數,解析映射文件中定義的動態 SQL 節點,造成數據庫能執行的SQL 語句。

  • SQL 執行

SQL 語句的執行涉及多個組件,包括 MyBatis 的四大核心,它們是: ExecutorStatementHandlerParameterHandlerResultSetHandler。SQL 的執行過程能夠用下面這幅圖來表示

image.png

MyBatis 層級結構各個組件的介紹(這裏只是簡單介紹,具體介紹在後面):

  • SqlSession: ,它是 MyBatis 核心 API,主要用來執行命令,獲取映射,管理事務。接收開發人員提供 Statement Id 和參數。並返回操做結果。
  • Executor :執行器,是 MyBatis 調度的核心,負責 SQL 語句的生成以及查詢緩存的維護。
  • StatementHandler : 封裝了JDBC Statement 操做,負責對 JDBC Statement 的操做,如設置參數、將Statement 結果集轉換成 List 集合。
  • ParameterHandler : 負責對用戶傳遞的參數轉換成 JDBC Statement 所須要的參數。
  • ResultSetHandler : 負責將 JDBC 返回的 ResultSet 結果集對象轉換成 List 類型的集合。
  • TypeHandler : 用於 Java 類型和 JDBC 類型之間的轉換。
  • MappedStatement : 動態 SQL 的封裝
  • SqlSource : 表示從 XML 文件或註釋讀取的映射語句的內容,它建立將從用戶接收的輸入參數傳遞給數據庫的 SQL。
  • Configuration: MyBatis 全部的配置信息都維持在 Configuration 對象之中。

基礎支持層

  • 反射模塊

Mybatis 中的反射模塊,對 Java 反射進行了很好的封裝,提供了簡易的 API,方便上層調用,而且對反射操做進行了一系列的優化,好比,緩存了類的 元數據(MetaClass)和對象的元數據(MetaObject),提升了反射操做的性能。

  • 類型轉換模塊

Mybatis 的別名機制,可以簡化配置文件,該機制是類型轉換模塊的主要功能之一。類型轉換模塊的另外一個功能是實現 JDBC 類型與 Java 類型的轉換。在 SQL 語句綁定參數時,會將數據由 Java 類型轉換成 JDBC 類型;在映射結果集時,會將數據由 JDBC 類型轉換成 Java 類型。

  • 日誌模塊

在 Java 中,有不少優秀的日誌框架,如 Log4j、Log4j二、slf4j 等。Mybatis 除了提供了詳細的日誌輸出信息,還可以集成多種日誌框架,其日誌模塊的主要功能就是集成第三方日誌框架。

  • 資源加載模塊

該模塊主要封裝了類加載器,肯定了類加載器的使用順序,並提供了加載類文件和其它資源文件的功能。

  • 解析器模塊

該模塊有兩個主要功能:一個是封裝了 XPath,爲 Mybatis 初始化時解析 mybatis-config.xml 配置文件以及映射配置文件提供支持;另外一個爲處理動態 SQL 語句中的佔位符提供支持。

  • 數據源模塊

Mybatis 自身提供了相應的數據源實現,也提供了與第三方數據源集成的接口。數據源是開發中的經常使用組件之一,不少開源的數據源都提供了豐富的功能,如鏈接池、檢測鏈接狀態等,選擇性能優秀的數據源組件,對於提供ORM 框架以及整個應用的性能都是很是重要的。

  • 事務管理模塊

通常地,Mybatis 與 Spring 框架集成,由 Spring 框架管理事務。但 Mybatis 自身對數據庫事務進行了抽象,提供了相應的事務接口和簡單實現。

  • 緩存模塊

Mybatis 中有一級緩存二級緩存,這兩級緩存都依賴於緩存模塊中的實現。可是須要注意,這兩級緩存與Mybatis 以及整個應用是運行在同一個 JVM 中的,共享同一塊內存,若是這兩級緩存中的數據量較大,則可能影響系統中其它功能,因此須要緩存大量數據時,優先考慮使用 Redis、Memcache 等緩存產品。

  • Binding 模塊

在調用 SqlSession 相應方法執行數據庫操做時,須要制定映射文件中定義的 SQL 節點,若是 SQL 中出現了拼寫錯誤,那就只能在運行時才能發現。爲了能儘早發現這種錯誤,Mybatis 經過 Binding 模塊將用戶自定義的Mapper 接口與映射文件關聯起來,系統能夠經過調用自定義 Mapper 接口中的方法執行相應的 SQL 語句完成數據庫操做,從而避免上述問題。注意,在開發中,咱們只是建立了 Mapper 接口,而並無編寫實現類,這是由於 Mybatis 自動爲 Mapper 接口建立了動態代理對象。

MyBatis 核心組件

在認識了 MyBatis 並瞭解其基礎架構以後,下面咱們來看一下 MyBatis 的核心組件,就是這些組件實現了從 SQL 語句到映射到 JDBC 再到數據庫字段之間的轉換,執行 SQL 語句並輸出結果集。首先來認識 MyBatis 的第一個核心組件

SqlSessionFactory

對於任何框架而言,在使用該框架以前都要經歷過一系列的初始化流程,MyBatis 也不例外。MyBatis 的初始化流程以下

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory.openSession();

上述流程中比較重要的一個對象就是SqlSessionFactory,SqlSessionFactory 是 MyBatis 框架中的一個接口,它主要負責的是

  • MyBatis 框架初始化操做
  • 爲開發人員提供SqlSession 對象

image.png

SqlSessionFactory 有兩個實現類,一個是 SqlSessionManager 類,一個是 DefaultSqlSessionFactory 類

  • DefaultSqlSessionFactory : SqlSessionFactory 的默認實現類,是真正生產會話的工廠類,這個類的實例的生命週期是全局的,它只會在首次調用時生成一個實例(單例模式),就一直存在直到服務器關閉。
  • SqlSessionManager : 已被廢棄,緣由大概是: SqlSessionManager 中須要維護一個本身的線程池,而使用MyBatis 更多的是要與 Spring 進行集成,並不會單獨使用,因此維護本身的 ThreadLocal 並無什麼意義,因此 SqlSessionManager 已經再也不使用。

SqlSessionFactory 的執行流程

下面來對 SqlSessionFactory 的執行流程來作一個分析

首先第一步是 SqlSessionFactory 的建立

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

從這行代碼入手,首先建立了一個 SqlSessionFactoryBuilder 工廠,這是一個建造者模式的設計思想,由 builder 建造者來建立 SqlSessionFactory 工廠

而後調用 SqlSessionFactoryBuilder 中的 build 方法傳遞一個InputStream 輸入流,Inputstream 輸入流中就是你傳過來的配置文件 mybatis-config.xml,SqlSessionFactoryBuilder 根據傳入的 InputStream 輸入流和environmentproperties屬性建立一個XMLConfigBuilder對象。SqlSessionFactoryBuilder 對象調用XMLConfigBuilder 的parse()方法,流程以下。

image.png

XMLConfigBuilder 會解析/configuration標籤,configuration 是 MyBatis 中最重要的一個標籤,下面流程會介紹 Configuration 標籤。

MyBatis 默認使用 XPath 來解析標籤,關於 XPath 的使用,參見 https://www.w3school.com.cn/x...

parseConfiguration 方法中,會對各個在 /configuration 中的標籤進行解析

image.png

重要配置

說一下這些標籤都是什麼意思吧

  • properties,外部屬性,這些屬性都是可外部配置且可動態替換的,既能夠在典型的 Java 屬性文件中配置,亦可經過 properties 元素的子元素來傳遞。
<properties>
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</properties>

通常用來給 environment 標籤中的 dataSource 賦值

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

還能夠經過外部屬性進行配置,可是咱們這篇文章以原理爲主,不會介紹太多應用層面的操做。

  • settings ,MyBatis 中極其重要的配置,它們會改變 MyBatis 的運行時行爲。

settings 中配置有不少,具體能夠參考 https://mybatis.org/mybatis-3... 詳細瞭解。這裏介紹幾個日常使用過程當中比較重要的配置

屬性 描述
cacheEnabled 全局地開啓或關閉配置文件中的全部映射器已經配置的任何緩存。
useGeneratedKeys 容許 JDBC 支持自動生成主鍵,須要驅動支持。 若是設置爲 true 則這個設置強制使用自動生成主鍵。
lazyLoadingEnabled 延遲加載的全局開關。當開啓時,全部關聯對象都會延遲加載。
jdbcTypeForNull 當沒有爲參數提供特定的 JDBC 類型時,爲空值指定 JDBC 類型。 某些驅動須要指定列的 JDBC 類型,多數狀況直接用通常類型便可,好比 NULL、VARCHAR 或 OTHER。
defaultExecutorType 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新。
localCacheScope MyBatis 利用本地緩存機制(Local Cache)防止循環引用(circular references)和加速重複嵌套查詢。 默認值爲 SESSION,這種狀況下會緩存一個會話中執行的全部查詢。 若設置值爲 STATEMENT,本地會話僅用在語句執行上,對相同 SqlSession 的不一樣調用將不會共享數據
proxyFactory 指定 Mybatis 建立具備延遲加載能力的對象所用到的代理工具。
mapUnderscoreToCamelCase 是否開啓自動駝峯命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的相似映射。

通常使用以下配置

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
</settings>
  • typeAliases,類型別名,類型別名是爲 Java 類型設置的一個名字。 它只和 XML 配置有關。
<typeAliases>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

當這樣配置時,Blog 能夠用在任何使用 domain.blog.Blog 的地方。

  • typeHandlers,類型處理器,不管是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,仍是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。

org.apache.ibatis.type 包下有不少已經實現好的 TypeHandler,能夠參考以下

image.png

你能夠重寫類型處理器或建立你本身的類型處理器來處理不支持的或非標準的類型。

具體作法爲:實現 org.apache.ibatis.type.TypeHandler 接口, 或繼承一個很方便的類 org.apache.ibatis.type.BaseTypeHandler, 而後能夠選擇性地將它映射到一個 JDBC 類型。

  • objectFactory,對象工廠,MyBatis 每次建立結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成。默認的對象工廠須要作的僅僅是實例化目標類,要麼經過默認構造方法,要麼在參數映射存在的時候經過參數構造方法來實例化。若是想覆蓋對象工廠的默認行爲,則能夠經過建立本身的對象工廠來實現。
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}

而後須要在 XML 中配置此對象工廠

<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>
  • plugins,插件開發,插件開發是 MyBatis 設計人員給開發人員留給自行開發的接口,MyBatis 容許你在已映射語句執行過程當中的某一點進行攔截調用。MyBatis 容許使用插件來攔截的方法調用包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler 接口,這幾個接口也是 MyBatis 中很是重要的接口,咱們下面會詳細介紹這幾個接口。
  • environments,MyBatis 環境配置,MyBatis 能夠配置成適應多種環境,這種機制有助於將 SQL 映射應用於多種數據庫之中。例如,開發、測試和生產環境須要有不一樣的配置;或者想在具備相同 Schema 的多個生產數據庫中 使用相同的 SQL 映射。

    這裏注意一點,雖然 environments 能夠指定多個環境,可是 SqlSessionFactory 只能有一個,爲了指定建立哪一種環境,只要將它做爲可選的參數傳遞給 SqlSessionFactoryBuilder 便可。

    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

    環境配置以下

    <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC">
          <property name="..." value="..."/>
        </transactionManager>
        <dataSource type="POOLED">
          <property name="driver" value="${driver}"/>
          <property name="url" value="${url}"/>
          <property name="username" value="${username}"/>
          <property name="password" value="${password}"/>
        </dataSource>
      </environment>
    </environments>
  • databaseIdProvider ,數據庫廠商標示,MyBatis 能夠根據不一樣的數據庫廠商執行不一樣的語句,這種多廠商的支持是基於映射語句中的 databaseId 屬性。

    <databaseIdProvider type="DB_VENDOR">
      <property name="SQL Server" value="sqlserver"/>
      <property name="DB2" value="db2"/>
      <property name="Oracle" value="oracle" />
    </databaseIdProvider>
  • mappers,映射器,這是告訴 MyBatis 去哪裏找到這些 SQL 語句,mappers 映射配置有四種方式

    <!-- 使用相對於類路徑的資源引用 -->
    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    
    <!-- 使用徹底限定資源定位符(URL) -->
    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      <mapper url="file:///var/mappers/BlogMapper.xml"/>
      <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    
    <!-- 使用映射器接口實現類的徹底限定類名 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
    <!-- 將包內的映射器接口實現所有註冊爲映射器 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>

上面的一個個屬性都對應着一個解析方法,都是使用 XPath 把標籤進行解析,解析完成後返回一個 DefaultSqlSessionFactory 對象,它是 SqlSessionFactory 的默認實現類。這就是 SqlSessionFactoryBuilder 的初始化流程,經過流程咱們能夠看到,初始化流程就是對一個個 /configuration 標籤下子標籤的解析過程。

SqlSession

在 MyBatis 初始化流程結束,也就是 SqlSessionFactoryBuilder -> SqlSessionFactory 的獲取流程後,咱們就能夠經過 SqlSessionFactory 對象獲得 SqlSession 而後執行 SQL 語句了。具體來看一下這個過程

image.png

在 SqlSessionFactory.openSession 過程當中咱們能夠看到,會調用到 DefaultSqlSessionFactory 中的 openSessionFromDataSource 方法,這個方法主要建立了兩個與咱們分析執行流程重要的對象,一個是 Executor 執行器對象,一個是 SqlSession 對象。執行器咱們下面會說,如今來講一下 SqlSession 對象

SqlSession 對象是 MyBatis 中最重要的一個對象,這個接口可以讓你執行命令,獲取映射,管理事務。SqlSession 中定義了一系列模版方法,讓你可以執行簡單的 CRUD 操做,也能夠經過 getMapper 獲取 Mapper 層,執行自定義 SQL 語句,由於 SqlSession 在執行 SQL 語句以前是須要先開啓一個會話,涉及到事務操做,因此還會有 commitrollbackclose 等方法。這也是模版設計模式的一種應用。

MapperProxy

MapperProxy 是 Mapper 映射 SQL 語句的關鍵對象,咱們寫的 Dao 層或者 Mapper 層都是經過 MapperProxy 來和對應的 SQL 語句進行綁定的。下面咱們就來解釋一下綁定過程

image.png

這就是 MyBatis 的核心綁定流程,咱們能夠看到 SqlSession 首先調用 getMapper 方法,咱們剛纔說到 SqlSession 是大哥級別的人物,只定義標準(有一句話是怎麼說的來着,一流的企業作標準,二流的企業作品牌,三流的企業作產品)。

SqlSession 不肯意作的事情交給 Configuration 這個手下去作,可是 Configuration 也是有小弟的,它不肯意作的事情直接甩給小弟去作,這個小弟是誰呢?它就是 MapperRegistry,立刻就到核心部分了。MapperRegistry 至關於項目經理,項目經理只從大面上把握項目進度,不須要知道手下的小弟是如何工做的,把任務完成了就好。最終真正幹活的仍是 MapperProxyFactory。看到這段代碼 Proxy.newProxyInstance ,你是否是有一種恍然大悟的感受,若是你沒有的話,建議查閱一下動態代理的文章,這裏推薦一篇 (https://www.jianshu.com/p/959...

也就是說,MyBatis 中 Mapper 和 SQL 語句的綁定正是經過動態代理來完成的。

經過動態代理,咱們就能夠方便的在 Dao 層或者 Mapper 層定義接口,實現自定義的增刪改查操做了。那麼具體的執行過程是怎麼樣呢?上面只是綁定過程,彆着急,下面就來探討一下 SQL 語句的執行過程。

image.png

有一部分代碼被遮擋,代碼有些多,不過不影響咱們看主要流程

MapperProxyFactory 會生成代理對象,這個對象就是 MapperProxy,最終會調用到 mapperMethod.execute 方法,execute 方法比較長,其實邏輯比較簡單,就是判斷是 插入更新刪除 仍是 查詢 語句,其中若是是查詢的話,還會判斷返回值的類型,咱們能夠點進去看一下都是怎麼設計的。

image.png

不少代碼其實能夠忽略,只看我標出來的重點就行了,咱們能夠看到,無論你前面通過多少道關卡處理,最終都逃不過 SqlSession 這個老大制定的標準。

咱們以 selectList 爲例,來看一下下面的執行過程。

image.png

這是 DefaultSqlSession 中 selectList 的代碼,咱們能夠看到出現了 executor,這是什麼呢?咱們下面來解釋。

Executor

還記得咱們以前的流程中提到了 Executor(執行器) 這個概念嗎?咱們來回顧一下它第一次出現的位置。

image.png

由 Configuration 對象建立了一個 Executor 對象,這個 Executor 是幹嗎的呢?下面咱們就來認識一下

Executor 的繼承結構

每個 SqlSession 都會擁有一個 Executor 對象,這個對象負責增刪改查的具體操做,咱們能夠簡單的將它理解爲 JDBC 中 Statement 的封裝版。 也能夠理解爲 SQL 的執行引擎,要幹活總得有一個發起人吧,能夠把 Executor 理解爲發起人的角色。

首先先從 Executor 的繼承體系來認識一下

image.png

如上圖所示,位於繼承體系最頂層的是 Executor 執行器,它有兩個實現類,分別是BaseExecutorCachingExecutor

BaseExecutor 是一個抽象類,這種經過抽象的實現接口的方式是適配器設計模式之接口適配 的體現,是Executor 的默認實現,實現了大部分 Executor 接口定義的功能,下降了接口實現的難度。BaseExecutor 的子類有三個,分別是 SimpleExecutor、ReuseExecutor 和 BatchExecutor。

SimpleExecutor : 簡單執行器,是 MyBatis 中默認使用的執行器,每執行一次 update 或 select,就開啓一個Statement 對象,用完就直接關閉 Statement 對象(能夠是 Statement 或者是 PreparedStatment 對象)

ReuseExecutor : 可重用執行器,這裏的重用指的是重複使用 Statement,它會在內部使用一個 Map 把建立的Statement 都緩存起來,每次執行 SQL 命令的時候,都會去判斷是否存在基於該 SQL 的 Statement 對象,若是存在 Statement 對象而且對應的 connection 尚未關閉的狀況下就繼續使用以前的 Statement 對象,並將其緩存起來。由於每個 SqlSession 都有一個新的 Executor 對象,因此咱們緩存在 ReuseExecutor 上的 Statement做用域是同一個 SqlSession。

BatchExecutor : 批處理執行器,用於將多個 SQL 一次性輸出到數據庫

CachingExecutor: 緩存執行器,先從緩存中查詢結果,若是存在就返回以前的結果;若是不存在,再委託給Executor delegate 去數據庫中取,delegate 能夠是上面任何一個執行器。

Executor 的建立和選擇

咱們上面提到 Executor 是由 Configuration 建立的,Configuration 會根據執行器的類型建立,以下

image.png

這一步就是執行器的建立過程,根據傳入的 ExecutorType 類型來判斷是哪一種執行器,若是不指定 ExecutorType ,默認建立的是簡單執行器。它的賦值能夠經過兩個地方進行賦值:

  • 能夠經過<settings>標籤來設置當前工程中全部的 SqlSession 對象使用默認的 Executor
<settings>
 <!--取值範圍 SIMPLE, REUSE, BATCH -->
    <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
  • 另一種直接經過Java對方法賦值的方式
session = factory.openSession(ExecutorType.BATCH);

Executor 的具體執行過程

Executor 中的大部分方法的調用鏈實際上是差很少的,下面是深刻源碼分析執行過程,若是你沒有時間或者暫時不想深刻研究的話,給你下面的執行流程圖做爲參考。

image.png

咱們緊跟着上面的 selectList 繼續分析,它會調用到 executor.query 方法。

當有一個查詢請求訪問的時候,首先會通過 Executor 的實現類 CachingExecutor ,先從緩存中查詢 SQL 是不是第一次執行,若是是第一次執行的話,那麼就直接執行 SQL 語句,並建立緩存,若是第二次訪問相同的 SQL 語句的話,那麼就會直接從緩存中提取。

image.png

上面這段代碼是從 selectList -> 從緩存中 query 的具體過程。可能你看到這裏有些以爲類都是什麼東西,我想鼓勵你一下,把握重點,不用每段代碼都看,從找到 SQL 的調用鏈路,其餘代碼想看的時候在看,看源碼就是很容易發矇,容易煩躁,可是切記一點,把握重點。

image.png

上面代碼會判斷緩存中是否有這條 SQL 語句的執行結果,若是沒有的話,就再從新建立 Executor 執行器執行 SQL 語句,注意, list = doQuery 是真正執行 SQL 語句的過程,這個過程當中會建立咱們上面提到的三種執行器,這裏咱們使用的是簡單執行器。

到這裏,執行器所作的工做就完事了,Executor 會把後續的工做交給 StatementHandler 繼續執行。下面咱們來認識一下 StatementHandler

StatementHandler

StatementHandler 是四大組件中最重要的一個對象,負責操做 Statement 對象與數據庫進行交互,在工做時還會使用 ParameterHandlerResultSetHandler對參數進行映射,對結果進行實體類的綁定,這兩個組件咱們後面說。

咱們在搭建原生 JDBC 的時候,會有這樣一行代碼

Statement stmt = conn.createStatement(); //也可使用PreparedStatement來作

這行代碼建立的 Statement 對象或者是 PreparedStatement 對象就是由 StatementHandler 進行管理的。

StatementHandler 的繼承結構

image.png

有沒有感受和 Executor 的繼承體系很類似呢?最頂級接口是四大組件對象,分別有兩個實現類 BaseStatementHandlerRoutingStatementHandler ,BaseStatementHandler 有三個實現類, 他們分別是 SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler。

RoutingStatementHandler : RoutingStatementHandler 並無對 Statement 對象進行使用,只是根據StatementType 來建立一個代理,代理的就是對應Handler的三種實現類。在MyBatis工做時,使用的StatementHandler 接口對象實際上就是 RoutingStatementHandler 對象。

BaseStatementHandler : 是 StatementHandler 接口的另外一個實現類,它自己是一個抽象類,用於簡化StatementHandler 接口實現的難度,屬於適配器設計模式體現,它主要有三個實現類

  • SimpleStatementHandler: 管理 Statement 對象並向數據庫中推送不須要預編譯的SQL語句。
  • PreparedStatementHandler: 管理 Statement 對象並向數據中推送須要預編譯的SQL語句。
  • CallableStatementHandler:管理 Statement 對象並調用數據庫中的存儲過程。
這裏注意一下,SimpleStatementHandler 和 PreparedStatementHandler 的區別是 SQL 語句是否包含變量,是否經過外部進行參數傳入。

SimpleStatementHandler 用於執行沒有任何參數傳入的 SQL

PreparedStatementHandler 須要對外部傳入的變量和參數進行提早參數綁定和賦值。

StatementHandler 的建立和源碼分析

咱們繼續來分析上面 query 的調用鏈路,StatementHandler 的建立過程以下

image.png

MyBatis 會根據 SQL 語句的類型進行對應 StatementHandler 的建立。咱們以預處理 StatementHandler 爲例來說解一下

image.png

執行器不只掌管着 StatementHandler 的建立,還掌管着建立 Statement 對象,設置參數等,在建立完 PreparedStatement 以後,咱們須要對參數進行處理了。

若是用一副圖來表示一下這個執行流程的話我想是這樣

image.png

這裏咱們先暫停一下,來認識一下第三個核心組件 ParameterHandler

ParameterHandler

ParameterHandler 介紹

ParameterHandler 相比於其餘的組件就簡單不少了,ParameterHandler 譯爲參數處理器,負責爲 PreparedStatement 的 sql 語句參數動態賦值,這個接口很簡單隻有兩個方法

image.png

ParameterHandler 只有一個實現類 DefaultParameterHandler , 它實現了這兩個方法。

  • getParameterObject: 用於讀取參數
  • setParameters: 用於對 PreparedStatement 的參數賦值

ParameterHandler 的解析過程

上面咱們討論過了 ParameterHandler 的建立過程,下面咱們繼續上面 parameterSize 流程

image.png

這就是具體參數的解析過程了,下面咱們來描述一下

public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  // parameterMappings 就是對 #{} 或者 ${} 裏面參數的封裝
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    // 若是是參數化的SQL,便須要循環取出並設置參數的值
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      // 若是參數類型不是 OUT ,這個類型與 CallableStatementHandler 有關
      // 由於存儲過程不存在輸出參數,因此參數不是輸出參數的時候,就須要設置。
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        // 獲得 #{}  中的屬性名
        String propertyName = parameterMapping.getProperty();
        // 若是 propertyName 是 Map 中的key
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          // 經過key 來獲得 additionalParameter 中的value值
          value = boundSql.getAdditionalParameter(propertyName);
        }
        // 若是不是 additionalParameters 中的key,並且傳入參數是 null, 則value 就是null
        else if (parameterObject == null) {
          value = null;
        }
        // 若是 typeHandlerRegistry 中已經註冊了這個參數的 Class 對象,即它是 Primitive 或者是String 的話
        else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          // 不然就是 Map
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // 在經過 SqlSource 的parse 方法獲得parameterMappings 的具體實現中,咱們會獲得parameterMappings 的 typeHandler
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        // 獲取 typeHandler 的jdbc type
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        } catch (SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}

下面用一個流程圖表示一下 ParameterHandler 的解析過程,以簡單執行器爲例

image.png

咱們在完成 ParameterHandler 對 SQL 參數的預處理後,回到 SimpleExecutor 中的 doQuery 方法

image.png

上面又引出來了一個重要的組件那就是 ResultSetHandler,下面咱們來認識一下這個組件

ResultSetHandler

ResultSetHandler 簡介

ResultSetHandler 也是一個很是簡單的接口

image.png

ResultSetHandler 是一個接口,它只有一個默認的實現類,像是 ParameterHandler 同樣,它的默認實現類是DefaultResultSetHandler

ResultSetHandler 解析過程

MyBatis 只有一個默認的實現類就是 DefaultResultSetHandler,DefaultResultSetHandler 主要負責處理兩件事

  • 處理 Statement 執行後產生的結果集,生成結果列表
  • 處理存儲過程執行後的輸出參數

按照 Mapper 文件中配置的 ResultType 或 ResultMap 來封裝成對應的對象,最後將封裝的對象返回便可。

public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  final List<Object> multipleResults = new ArrayList<Object>();

  int resultSetCount = 0;
  // 獲取第一個結果集
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 獲取結果映射
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  // 結果映射的大小
  int resultMapCount = resultMaps.size();
  // 校驗結果映射的數量
  validateResultMapsCount(rsw, resultMapCount);
  // 若是ResultSet 包裝器不是null, 而且 resultmap 的數量  >  resultSet 的數量的話
  // 由於 resultSetCount 第一次確定是0,因此直接判斷 ResultSetWrapper 是否爲 0 便可
  while (rsw != null && resultMapCount > resultSetCount) {
    // 從 resultMap 中取出 resultSet 數量
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 處理結果集, 關閉結果集
    handleResultSet(rsw, resultMap, multipleResults, null);
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  // 從 mappedStatement 取出結果集
  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

  return collapseSingleResultList(multipleResults);
}

其中涉及的主要對象有:

ResultSetWrapper : 結果集的包裝器,主要針對結果集進行的一層包裝,它的主要屬性有

  • ResultSet : Java JDBC ResultSet 接口表示數據庫查詢的結果。 有關查詢的文本顯示瞭如何將查詢結果做爲java.sql.ResultSet 返回。 而後迭代此ResultSet以檢查結果。
  • TypeHandlerRegistry: 類型註冊器,TypeHandlerRegistry 在初始化的時候會把全部的 Java類型和類型轉換器進行註冊。
  • ColumnNames: 字段的名稱,也就是查詢操做須要返回的字段名稱
  • ClassNames: 字段的類型名稱,也就是 ColumnNames 每一個字段名稱的類型
  • JdbcTypes: JDBC 的類型,也就是 java.sql.Types 類型
  • ResultMap: 負責處理更復雜的映射關係

在 DefaultResultSetHandler 中處理完結果映射,並把上述結構返回給調用的客戶端,從而執行完成一條完整的SQL語句。

文章參考:

MyBatis的優缺點以及特色

mybatis基礎,mybatis核心配置文件properties元素

https://mybatis.org/mybatis-3...

深刻淺出Mybatis系列(十)---SQL執行流程分析(源碼篇)

https://www.jianshu.com/p/196...

http://www.mybatis.org/mybati...

https://www.cnblogs.com/virgo...

https://blog.csdn.net/Roger_C...

https://blog.csdn.net/qq92486...

[mybatis 源碼分析(八)ResultSetHandler 詳解](

image.png

相關文章
相關標籤/搜索