Mybatis架構(gòu)分析和工作原理

Mybatis工作流程

工作流程圖

1、解析配置文件,包括全局配置文件和映射器配置文件乘凸,把它們解析成一個(gè) Configuration 對(duì)象鸥拧。
2党远、創(chuàng)建會(huì) 話 工 廠 SqlSessionFactory
3、操作數(shù)據(jù)庫(kù)的接口富弦,它在應(yīng)用程序和數(shù)據(jù)庫(kù)中間沟娱,代表我們跟數(shù)據(jù)庫(kù)之間的一次連接:創(chuàng)建 SqlSession 對(duì)象。
4腕柜、SqlSession 持有了一個(gè) Executor 對(duì)象济似,用來(lái)封裝對(duì)數(shù)據(jù)庫(kù)的操作矫废。
5、在執(zhí)行器 Executor 執(zhí)行 query 或者 update 操作的時(shí)候我們創(chuàng)建一系列的對(duì)象砰蠢,來(lái)處理參數(shù)蓖扑、執(zhí)行 SQL、處理結(jié)果集台舱。

架構(gòu)分層和模塊劃分

github代碼

代碼架構(gòu).png

架構(gòu)圖.png

接口層
接口層是最重要的一層律杠,核心對(duì)象是 SqlSession,它是上層應(yīng)用和 MyBatis打交道的橋梁竞惋,SqlSession 上定義了非常多的對(duì)數(shù)據(jù)庫(kù)的操作方法柜去。接口層在接收到調(diào)用請(qǐng)求的時(shí)候,會(huì)調(diào)用核心處理層的相應(yīng)模塊來(lái)完成具體的數(shù)據(jù)庫(kù)操作碰声。

核心處理層
所有跟數(shù)據(jù)庫(kù)操作相關(guān)的動(dòng)作都是在這一層完成的诡蜓,插件也屬于核心層,這是由它的工作方式和攔截的對(duì)象決定的胰挑。

主要做的事情:

  1. 把接口中傳入的參數(shù)解析并且映射成 JDBC 類(lèi)型蔓罚;
  2. 解析 xml 文件中的 SQL 語(yǔ)句,包括插入?yún)?shù)瞻颂,和動(dòng)態(tài) SQL 的生成豺谈;
  3. 執(zhí)行 SQL 語(yǔ)句;
  4. 處理結(jié)果集贡这,并映射成 Java 對(duì)象茬末。

基礎(chǔ)支持層
基礎(chǔ)支持層主要是一些抽取出來(lái)的通用的功能(實(shí)現(xiàn)復(fù)用),用來(lái)支持核心處理層的功能盖矫。比如數(shù)據(jù)源丽惭、緩存、日志辈双、xml 解析责掏、反射、IO湃望、事務(wù)等等這些功能换衬。

Mybatis緩存

緩存是一般的 ORM 框架都會(huì)提供的功能,目的就是提升查詢(xún)的效率和減少數(shù)據(jù)庫(kù)的壓力证芭。跟 Hibernate 一樣瞳浦,MyBatis 也有一級(jí)緩存和二級(jí)緩存,并且預(yù)留了集成第三方緩存的接口废士。

緩存體系結(jié)構(gòu)
MyBatis 跟緩存相關(guān)的類(lèi)都在 cache 包里面叫潦,其中有一個(gè) Cache 接口,只有一個(gè)默認(rèn)的實(shí)現(xiàn)類(lèi) PerpetualCache官硝,它是用 HashMap 實(shí)現(xiàn)的诅挑。

這里用到了裝飾器模式四敞,通過(guò)這些裝飾器可以額外實(shí)現(xiàn)很多的功能:回收策略、日志記錄拔妥、定時(shí)刷新等等。

通過(guò)查看代碼达箍,緩存實(shí)現(xiàn)類(lèi)總體可分為三類(lèi):基本緩存没龙、淘汰算法緩存、裝飾器緩存

緩存實(shí)現(xiàn)類(lèi) 描述 作用 裝飾條件
基本緩存 緩存基本實(shí)現(xiàn)類(lèi) 默認(rèn)是 PerpetualCache缎玫,也可以自定義比如RedisCache硬纤、EhCache 等,具備基本功能的緩存類(lèi) 無(wú)
LruCache LRU 策略的緩存 當(dāng)緩存到達(dá)上限時(shí)候赃磨,刪除最近最少使用的緩存(Least Recently Use) eviction="LRU"(默認(rèn))
FifoCache FIFO 策略的緩存 當(dāng)緩存到達(dá)上限時(shí)候筝家,刪除最先入隊(duì)的緩存 eviction="FIFO"
SoftCache、WeakCache 帶清理策略的緩存 通過(guò) JVM 的軟引用和弱引用來(lái)實(shí)現(xiàn)緩存邻辉,當(dāng) JVM內(nèi)存不足時(shí)溪王,會(huì)自動(dòng)清理掉這些緩存,基于SoftReference 和 WeakReference eviction="SOFT" eviction="WEAK"
LoggingCache 帶日志功能的緩存 比如:輸出緩存命中率 基本
SynchronizedCache 同步緩存 基于 synchronized 關(guān)鍵字實(shí)現(xiàn)值骇,解決并發(fā)問(wèn)題 基本
BlockingCache 阻塞緩存 通過(guò)在 get/put 方式中加鎖莹菱,保證只有一個(gè)線程操作緩存,基于 Java 重入鎖實(shí)現(xiàn) blocking=true
SerializedCache 支持序列化的緩存 將對(duì)象序列化以后存到緩存中吱瘩,取出時(shí)反序列化 readOnly=false(默認(rèn))
ScheduledCache 定時(shí)調(diào)度的緩存 在進(jìn)行 get/put/remove/getSize 等操作前道伟,判斷緩存時(shí)間是否超過(guò)了設(shè)置的最長(zhǎng)緩存時(shí)間(默認(rèn)是一小時(shí)),如果是則清空緩存--即每隔一段時(shí)間清空一次緩存 flushInterval 不為空
TransactionalCache 事務(wù)緩存 在二級(jí)緩存中使用使碾,可一次存入多個(gè)緩存蜜徽,移除多個(gè)緩存 在TransactionalCacheManager 中用 Map維護(hù)對(duì)應(yīng)關(guān)系

一級(jí)緩存

一級(jí)緩存也叫本地緩存,MyBatis 的一級(jí)緩存是在會(huì)話(SqlSession)層面進(jìn)行緩存的票摇。MyBatis 的一級(jí)緩存是默認(rèn)開(kāi)啟的拘鞋,不需要任何的配置。

一級(jí)緩存.png

如果要在同一個(gè)會(huì)話里面共享一級(jí)緩存兄朋,這個(gè)對(duì)象肯定是在 SqlSession 里面創(chuàng)建的掐禁,作為 SqlSession 的一個(gè)屬性。而DefaultSqlSession實(shí)現(xiàn)了SqlSession颅和,對(duì)應(yīng)的要實(shí)現(xiàn)他的所有方法傅事。

DefaultSqlSession 里面只有兩個(gè)屬性,Configuration 是全局的峡扩,所以緩存只可能放在 Executor 里面維護(hù)蹭越,SimpleExecutor /ReuseExecutor/BatchExecutor 的父類(lèi)BaseExecutor 的構(gòu)造函數(shù)中持有了 PerpetualCache。

同一個(gè)會(huì)話里面教届,多次執(zhí)行相同的 SQL 語(yǔ)句响鹃,會(huì)直接從內(nèi)存取到緩存的結(jié)果驾霜,不會(huì)再發(fā)送 SQL 到數(shù)據(jù)庫(kù)。但是不同的會(huì)話里面买置,即使執(zhí)行的 SQL 一模一樣(通過(guò)一個(gè)Mapper 的同一個(gè)方法的相同參數(shù)調(diào)用)粪糙,也不能使用到一級(jí)緩存。

一級(jí)緩存的缺點(diǎn)
使用一級(jí)緩存的時(shí)候忿项,因?yàn)榫彺?strong>不能跨會(huì)話共享蓉冈,不同的會(huì)話之間對(duì)于相同的數(shù)據(jù)可能有不一樣的緩存。在有多個(gè)會(huì)話或者分布式環(huán)境下轩触,會(huì)存在臟數(shù)據(jù)的問(wèn)題寞酿。如果要解決這個(gè)問(wèn)題,就要用到二級(jí)緩存脱柱。

二級(jí)緩存

二級(jí)緩存是用來(lái)解決一級(jí)緩存不能跨會(huì)話共享的問(wèn)題的伐弹,范圍是 namespace 級(jí)別的,可以被多個(gè) SqlSession 共享(只要是同一個(gè)接口里面的相同方法榨为,都可以共享)惨好,生命周期和應(yīng)用同步。二級(jí)緩存是默認(rèn)關(guān)閉的

一級(jí)緩存和二級(jí)緩存同時(shí)存在的時(shí)候柠逞,那個(gè)先執(zhí)行昧狮?

二級(jí)緩存作為一個(gè)作用范圍更廣的緩存,他是在SqlSession的外層板壮,否則不可能被多個(gè)SqlSession共享逗鸣,而一級(jí)緩存是在SqlSession的內(nèi)部的,所以在工作在一級(jí)緩存之前绰精,也就是說(shuō)只有取不到二級(jí)情況下才到會(huì)話中去取一級(jí)緩存

二級(jí)緩存在哪里維護(hù)的撒璧?

由于二級(jí)緩存是跨會(huì)話共享的,SqlSession本身和它里面的BaseExecutor已經(jīng)滿足不了需求了笨使,所以需要用到CachingExecutor這個(gè)類(lèi)卿樱,啟用了二級(jí)緩存的話,CachingExecutor對(duì)于查詢(xún)請(qǐng)求硫椰,會(huì)判斷二級(jí)緩存中是否有緩存結(jié)果繁调,如果有就直接返回,如果沒(méi)有則委派交給真正的查詢(xún)器Executor實(shí)現(xiàn)類(lèi)

二級(jí)緩存.png
開(kāi)啟二級(jí)緩存

1靶草、在 mybatis-config.xml 中配置了(可以不配置蹄胰,默認(rèn)是 true),只要沒(méi)有顯式地設(shè)置 cacheEnabled=false奕翔,都會(huì)用 CachingExecutor 裝飾基本的執(zhí)行器裕寨。
<setting name" ="cacheEnabled" value ="true"/>

2、在 Mapper.xml 中配置<cache/>標(biāo)簽

<!-- 聲明這個(gè) namespace 使用二級(jí)緩存 -->
<cache  type ="org.apache.ibatis.cache.impl.PerpetualCache"
    size ="1024" <!—最多緩存對(duì)象個(gè)數(shù),默認(rèn) 1024-->
    eviction ="LRU" <!—回收策略-->
    flushInterval ="120000" <!—自動(dòng)刷新時(shí)間 ms宾袜,未配置時(shí)只有調(diào)用時(shí)刷新-->
    readOnly =" false" "/> <!— 默認(rèn)是  false(安全)捻艳,改為 true 可讀寫(xiě)時(shí),對(duì)象必須支持序列化 -->

cache屬性介紹:

屬性 含義 取值
type 緩存實(shí)現(xiàn)類(lèi) 需要實(shí)現(xiàn) Cache 接口庆猫,默認(rèn)是 PerpetualCache
size 最多緩存對(duì)象個(gè)數(shù) 默認(rèn)1024
eviction 回收策略(緩存淘汰算法) LRU – 最近最少使用的:移除最長(zhǎng)時(shí)間不被使用的對(duì)象(默認(rèn))认轨。FIFO – 先進(jìn)先出:按對(duì)象進(jìn)入緩存的順序來(lái)移除它們。SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象月培。WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象好渠。
flushInterval 定時(shí)自動(dòng)清空緩存間隔 自動(dòng)刷新時(shí)間,單位 ms节视,未配置時(shí)只有調(diào)用時(shí)刷新
readOnly 是否只讀 true:只讀緩存;會(huì)給所有調(diào)用者返回緩存對(duì)象的相同實(shí)例假栓。因此這些對(duì)象不能被修改寻行。這提供了很重要的性能優(yōu)勢(shì)。false:讀寫(xiě)緩存匾荆;會(huì)返回緩存對(duì)象的拷貝(通過(guò)序列化)拌蜘,不會(huì)共享。這會(huì)慢一些牙丽,但是安全简卧,因此默認(rèn)是 false。改為 false 可讀寫(xiě)時(shí)烤芦,對(duì)象必須支持序列化举娩。
blocking 是否使用可重入鎖實(shí)現(xiàn)緩存的并發(fā)控制 true,會(huì)使用 BlockingCache 對(duì) Cache 進(jìn)行裝飾默認(rèn) false

Mapper.xml 配置了<cache>之后构罗,select()會(huì)被緩存铜涉。update()、delete()遂唧、insert()會(huì)刷新緩存芙代。

在單個(gè) Statement ID 上顯式關(guān)閉二級(jí)緩存(默認(rèn)是 true)
<select id" ="selectBlog" resultMap" ="BaseResultMap" useCache ="false">

驗(yàn)證二級(jí)緩存(需要先開(kāi)啟)

1、事務(wù)不提交盖彭,二級(jí)緩存不存在
因?yàn)槎?jí)緩存使用 TransactionalCacheManager(TCM)來(lái)管理纹烹,最后又調(diào)用了 TransactionalCache的getObject()、putObject和commit()方法召边,TransactionalCache里面又持有了真正的 Cache 對(duì)象铺呵,比如是經(jīng)過(guò)層層裝飾的 PerpetualCache。

在 putObject 的時(shí)候掌实,只是添加到了 entriesToAddOnCommit 里面陪蜻,只有它的commit()方法被調(diào)用的時(shí)候才會(huì)調(diào)用 flushPendingEntries()真正寫(xiě)入緩存。它就是在DefaultSqlSession 調(diào)用 commit()的時(shí)候被調(diào)用的贱鼻。

2宴卖、使用不同的 session 和 mapper滋将,二級(jí)緩存可以跨 session 存在

BlogMapper mapper1 = session1.getMapper(BlogMapper. class);
System. out .println(mapper1.selectBlogById(1));
// 事務(wù)不提交的情況下,二級(jí)緩存不會(huì)寫(xiě)入
session1.commit();
BlogMapper mapper2 = session2.getMapper(BlogMapper. class);
System. out .println(mapper2.selectBlogById(1));

3症昏、在其他的 session 中執(zhí)行增刪改操作随闽,驗(yàn)證緩存會(huì)被刷新(清空緩存)
在 CachingExecutor 的 update()方法里面會(huì)調(diào)用 flushCacheIfRequired(ms),isFlushCacheRequired 就是從標(biāo)簽里面渠道的 flushCache 的值肝谭。而增刪改操作的flushCache 屬性默認(rèn)為 true掘宪。

二級(jí)緩存開(kāi)啟的時(shí)機(jī)

1、因?yàn)樗械脑鰟h改都會(huì)刷新二級(jí)緩存攘烛,導(dǎo)致二級(jí)緩存失效魏滚,所以適合在查詢(xún)?yōu)橹鞯膽?yīng)用中使用,比如歷史交易坟漱、歷史訂單的查詢(xún)鼠次。否則緩存就失去了意義。

2芋齿、如果多個(gè) namespace 中有針對(duì)于同一個(gè)表的操作腥寇,比如 Blog 表,如果在一個(gè)namespace 中刷新了緩存觅捆,另一個(gè) namespace 中沒(méi)有刷新赦役,就會(huì)出現(xiàn)讀到臟數(shù)據(jù)的情況。所以栅炒,推薦在一個(gè) Mapper 里面只操作單表的情況使用掂摔。

第三方緩存做二級(jí)緩存

可以通過(guò)實(shí)現(xiàn) Cache 接口來(lái)自定義二級(jí)緩存。

MyBatis 官方提供了一些第三方緩存集成方式职辅,比如 ehcache 和 redis

操作步驟:

<!-- 1.pom 文件引入依賴(lài):-->
<dependency>
    < groupId>org.mybatis.caches</ groupId>
    < artifactId>mybatis-redis</ artifactId>
    < version>1.0.0-beta2</ version>
</dependency>

<!--2.Mapper.xml 配置棒呛,type 使用 RedisCache-->
<cache  type ="org.mybatis.caches.redis.RedisCache" eviction" ="FIFO"  flushInterval" ="60000"  size" ="512"  readOnly ="true"/>

<!--3.redis.properties 配置 -->
host= localhost
port= 6379
connectionTimeout= 5000
soTimeout= 5000
database=0 0

源碼解讀

分為四個(gè)步驟來(lái)分析:
1.配置解析
2.會(huì)話創(chuàng)建
3.獲取Mapper對(duì)象
4.執(zhí)行SQL

配置解析
配置解析的過(guò) 程全 部 只 解 析 了 兩 種 文 件 。 一個(gè)是mybatis-config.xml 全局配置文件域携,一個(gè)是很多的Mapper.xml文件

//解析文件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

解析配置文件之后new了一個(gè)SqlSessionFactoryBuilder(建造者模式)簇秒,返回了一個(gè)SqlSessionFactory對(duì)象(單例模式)

從SqlSessionFactoryBuilder#build()方法就是XMLConfigBuilder對(duì)象的解析

XMLConfigBuilder

這個(gè)類(lèi)是抽象類(lèi)BaseBuilder的一個(gè)子類(lèi),專(zhuān)門(mén)用來(lái)解析全局配置文件秀鞭,針對(duì)不同的構(gòu)建目標(biāo)還有其他的子類(lèi)趋观,比如:
XMLMapperBuilder:解析 Mapper 映射器
XMLStatementBuilder:解析增刪改查標(biāo)簽

解析文件流,創(chuàng)建了一個(gè)parser锋边,返回了一個(gè)Configuration類(lèi)皱坛,然后調(diào)用parser.parse()方法

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

這個(gè)方法首先會(huì)檢查是不是已經(jīng)解析過(guò),也就是說(shuō)在應(yīng)用的生命周期里面豆巨,config 配置文件只需要解析一次剩辟,生成的 Configuration 對(duì)象也會(huì)存在應(yīng)用的整個(gè)生命周期中。
parseConfiguration這個(gè)方法就是加載 config 文件里面的所有一級(jí)標(biāo)簽。

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      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"));
      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);
    }
  }

一級(jí)標(biāo)簽解析

propertiesElement()

第一個(gè)是解析<properties>標(biāo)簽贩猎,讀取我們引入的外部配置文件熊户。

這里面又有兩種類(lèi)型,一種是放在 resource 目錄下的吭服,是相對(duì)路徑嚷堡,一種是寫(xiě)的絕對(duì)路徑的。

解析的最終結(jié)果就是我們會(huì)把所有的配置信息放到名為 defaults 的 Properties 對(duì)象里面艇棕,最后把XPathParser 和 Configuration 的 Properties 屬性都設(shè)置成我們填充后的 Properties對(duì)象蝌戒。

settingsAsProperties) ()

把<settings>標(biāo)簽也解析成了一個(gè) Properties 對(duì)象,對(duì)于<settings>
標(biāo)簽的子標(biāo)簽的處理在后面沼琉。

loadCustomVfs(settings)

loadCustomVfs 是獲取 Vitual File System 的自定義實(shí)現(xiàn)類(lèi)北苟。
比如我們要讀取本地文件,或者 FTP 遠(yuǎn)程文件的時(shí)候打瘪,就可以用到自定義的 VFS 類(lèi)粹淋。我們根據(jù)<settings>標(biāo)簽里面的<vfsImpl>標(biāo)簽,生成了一個(gè)抽象類(lèi) VFS 的子類(lèi)瑟慈,并且賦值到 Configuration中。

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
      String[] clazzes = value.split(",");
      for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
          @SuppressWarnings("unchecked")
          Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
          configuration.setVfsImpl(vfsImpl);
        }
      }
    }
  }
loadCustomLogImpl(settings)

loadCustomLogImpl 是根據(jù)<logImpl>標(biāo)簽獲取日志的實(shí)現(xiàn)類(lèi)屋匕,我們可以用到很多的日志的方案葛碧,包括 LOG4J,LOG4J2过吻,SLF4J 等等荐绝。這里生成了一個(gè) Log 接口的實(shí)現(xiàn)類(lèi)朱灿,并且賦值到 Configuration 中。

typeAliasesElement()

有兩種定義方式,一種是直接定義一個(gè)類(lèi)的別名褪子,一種就是指定一個(gè)包,那么這個(gè) package 下面所有的類(lèi)的名字就會(huì)成為這個(gè)類(lèi)全路徑的別名嗅蔬。

類(lèi)的別名和類(lèi)的關(guān)系蜘渣,我們放在一個(gè) TypeAliasRegistry 對(duì)象里面。

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
pluginElement()

插件標(biāo)簽杰刽,比如 Pagehelper 的翻頁(yè)插件菠发,或者我們自定義的插件。<plugins>標(biāo)簽里面只有<plugin>標(biāo)簽贺嫂,<plugin>標(biāo)簽里面只有<property>標(biāo)簽滓鸠。

標(biāo)簽解析完以后,會(huì)生成一個(gè) Interceptor 對(duì)象第喳,并且添加到 Configuration 的InterceptorChain 屬性里面糜俗,它是一個(gè) List。

objectFactoryElement() 、objectWrapperFactoryElement()

用來(lái)實(shí)例化對(duì)象悠抹,分 別 生 成 ObjectFactory 珠月、ObjectWrapperFactory 對(duì)象,同樣設(shè)置到 Configuration 的屬性里面锌钮。

reflectorFactoryElement()

解析 reflectorFactory 標(biāo)簽桥温,生成 ReflectorFactory 對(duì)象

settingsElement(settings)

對(duì)<settings>標(biāo)簽里面所有子標(biāo)簽的處理,所有的值梁丘,都會(huì)賦值到 Configuration 的屬性里面去侵浸。

private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }
environmentsElement()

一個(gè) environment 就是對(duì)應(yīng)一個(gè)數(shù)據(jù)源,所以在這里我們會(huì)根據(jù)配
置的<transactionManager>創(chuàng)建一個(gè)事務(wù)工廠氛谜,根據(jù)<dataSource>標(biāo)簽創(chuàng)建一個(gè)數(shù)據(jù)源掏觉,最后把這兩個(gè)對(duì)象設(shè)置成 Environment 對(duì)象的屬性,放到 Configuration 里面值漫。

databaseIdProviderElement()

解析 databaseIdProvider 標(biāo)簽澳腹,生成 DatabaseIdProvider 對(duì)象(用來(lái)支持不同廠商的數(shù)據(jù)庫(kù))。

typeHandlerElement()

TypeAlias 一樣杨何,TypeHandler 有兩種配置方式酱塔,一種是單獨(dú)配置一個(gè)類(lèi),一種是指定一個(gè) package危虱。最后我們得到的是 JavaType 和 JdbcType羊娃,以及用來(lái)做相互映射的 TypeHandler 之間的映射關(guān)系。
最后存放在 TypeHandlerRegistry 對(duì)象里面埃跷。

private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }
mapperElement()
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //包
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //相對(duì)路徑
          String resource = child.getStringAttribute("resource");
          //絕對(duì)路徑
          String url = child.getStringAttribute("url");
          //單個(gè)接口
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            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) {
            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<?> 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.");
          }
        }
      }
    }
  }

這個(gè)標(biāo)簽的解析主要做了幾個(gè)事情:
1.判斷
首先會(huì)判斷是不是接口蕊玷,只有接口才解析;然后判斷是不是已經(jīng)注冊(cè)了弥雹,單個(gè) Mapper重復(fù)注冊(cè)會(huì)拋出異常垃帅。
2.注冊(cè)
XMLMapperBuilder.parse()方法,是對(duì) Mapper 映射器的解析剪勿。里面有兩個(gè)方法:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

configurationElement()—— 解析所有的子標(biāo)簽 贸诚, 其中
buildStatementFromContext()最終獲得 MappedStatement 對(duì)象。

bindMapperForNamespace()——把 namespace(接口類(lèi)型)和工廠類(lèi)綁定起來(lái)厕吉。

無(wú)論是按 package 掃描赦颇,還是按接口掃描,最后都會(huì)調(diào)用到 MapperRegistry 的addMapper()方法赴涵。

MapperRegistry 里面維護(hù)的其實(shí)是一個(gè) Map 容器媒怯,存儲(chǔ)接口和代理工廠的映射關(guān)系。

3.注釋處理
除了解析映射文件之外髓窜,還會(huì)去解析 Mapper 接口方法上的注解扇苞,在 addMapper()方法里面創(chuàng)建了一個(gè) MapperAnnotationBuilder欺殿,調(diào)用parse()方法

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

parseCache() 和 parseCacheRef() 方 法 其 實(shí) 是 對(duì)@CacheNamespace 和@CacheNamespaceRef 這兩個(gè)注解的處理。

parseStatement()方法里面的各種 getAnnotation()鳖敷,都是對(duì)注解的解析脖苏,比如@Options,@SelectKey定踱,@ResultMap 等等棍潘。

最后同樣會(huì)解析成 MappedStatement 對(duì)象,也就是說(shuō)在 XML 中配置崖媚,和使用注解配置亦歉,最后起到一樣的效果。

4.處理完成
如果注冊(cè)沒(méi)有完成畅哑,還要從 Map 里面 remove 掉肴楷。

if (!loadCompleted) {
    knownMappers.remove(type);
}

MapperRegistry 也會(huì)放到 Configuration 里面去,最后調(diào)用另一個(gè) build()方法荠呐,返回 DefaultSqlSessionFactory赛蔫。

配置解析小結(jié)

1.主要完成了 config 配置文件、Mapper 文件泥张、Mapper 接口上的注解的解析呵恢。

2.得到了一個(gè)最重要的對(duì)象 Configuration,這里面存放了全部的配置信息媚创,它在屬性里面還有各種各樣的容器瑰剃。

3.返回了一個(gè) DefaultSqlSessionFactory,里面持有了 Configuration的實(shí)例筝野。

創(chuàng)建會(huì)話

數(shù)據(jù)庫(kù)的每一次連接,都需要?jiǎng)?chuàng)建一個(gè)會(huì)話粤剧,我們用openSession()方法來(lái)創(chuàng)建歇竟。

DefaultSqlSessionFactory#openSessionFromDataSource()

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);
      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();
    }
  }

由源碼可以得出:要先從 Configuration 里面拿到 Enviroment,Enviroment 獲取事務(wù)工廠抵恋,最后通過(guò)執(zhí)行器Executor 處理請(qǐng)求(sql)

1.創(chuàng)建Transaction
這里有2種方式獲然酪椤:JDBC、MANAGED

1.1.JDBC(JdbcTransactionFactory-> JdbcTransaction)
如果配置的是 JDBC弧关,則會(huì)使用 Connection 對(duì)象的 commit()盅安、rollback()、close()管理事務(wù)世囊。

1.2.MANAGED(ManagedTransactionFactory->ManagedTransaction)
如果配置成 MANAGED别瞭,會(huì)把事務(wù)交給容器來(lái)管理,比如 JBOSS株憾,Weblogic蝙寨。因?yàn)槲覀兣艿氖潜镜爻绦蛏柜茫绻渲贸?MANAGE 不會(huì)有任何事務(wù)。

PS:如 果 是 Spring + MyBatis墙歪,則 沒(méi) 有 必 要 配 置 听系, 因 為 我 們 會(huì) 直 接 在applicationContext.xml 里面配置數(shù)據(jù)源和事務(wù)管理器,覆蓋 MyBatis 的配置虹菲。

2.創(chuàng)建Executor(執(zhí)行器)
Executor 的基本類(lèi)型有三種:SIMPLE靠胜、BATCH、REUSE毕源,默認(rèn)是 SIMPLE(settingsElement()讀取默認(rèn)值)浪漠,他們都繼承了抽象類(lèi) BaseExecutor。

三種執(zhí)行器的區(qū)別:
SimpleExecutor:每執(zhí)行一次 update 或 select脑豹,就開(kāi)啟一個(gè)Statement 對(duì)象郑藏,用完立刻關(guān)閉 Statement 對(duì)象。

ReuseExecutor:執(zhí)行 update 或 select瘩欺,以 sql 作為 key 查找 Statement 對(duì)象必盖,存在就使用,不存在就創(chuàng)建俱饿,用完后歌粥,不關(guān)閉 Statement 對(duì)象,而是放置于 Map 內(nèi)拍埠,供下一次使用失驶。簡(jiǎn)言之,就是**重復(fù)使用 **Statement 對(duì)象枣购。

BatchExecutor:執(zhí)行 update(沒(méi)有 select嬉探,JDBC 批處理不支持 select),將所有 sql 都添加到批處理中(addBatch())棉圈,等待統(tǒng)一執(zhí)行(executeBatch())涩堤,它緩存了多個(gè) Statement 對(duì)象,每個(gè) Statement 對(duì)象都是 addBatch()完畢后分瘾,等待逐一執(zhí)行executeBatch()批處理胎围。與 JDBC 批處理相同。

小結(jié)

創(chuàng)建會(huì)話的過(guò)程德召,獲得了一個(gè)DefaultSqlSession白魂,里面包含了一個(gè)Executor,用來(lái)執(zhí)行SQL

獲取Mapper對(duì)象

獲得DefaultSqlSession之后上岗,必須找到 Mapper.xml 里面定義的Statement ID福荸,才能執(zhí)行對(duì)應(yīng)的 SQL 語(yǔ)句。

找到Statement ID有2中方式:
1.直接調(diào)用session的方法肴掷,在參數(shù)里面?zhèn)魅隨tatement ID(硬編碼)逞姿,如果寫(xiě)錯(cuò)了很難調(diào)試辞嗡,因?yàn)榫幾g的時(shí)候不會(huì)報(bào)錯(cuò),在用的時(shí)候才會(huì)說(shuō)找不到

2.定義一個(gè)接口滞造,在調(diào)用Mapper接口的方法续室,由于我們的接口名稱(chēng)跟 Mapper.xml 的 namespace 是對(duì)應(yīng)的,接口的方法跟statement ID 也都是對(duì)應(yīng)的谒养,可以快速定位(Mybatis后面的版本默認(rèn)用這種方式)

Mapper對(duì)象的獲韧φ?

在DefaultSqlSession#getMapper()方法买窟,調(diào)用了 Configuration#getMapper()方法丰泊,Configuration 的 getMapper()方法,又調(diào)用了 MapperRegistry 的 getMapper()方法始绍,在最后返回了一個(gè)mapperProxyFactory.newInstance(sqlSession)

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

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

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);
    }
  }

在解析 mapper 標(biāo)簽和 Mapper.xml 的時(shí)候已經(jīng)把接口類(lèi)型和類(lèi)型對(duì)應(yīng)的 MapperProxyFactory 放到了一個(gè) Map Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();中瞳购。獲取 Mapper 代理對(duì)象,實(shí)際上是從Map 中獲取對(duì)應(yīng)的工廠類(lèi)后mapperProxyFactory.newInstance(sqlSession)

在這里用到了動(dòng)態(tài)代理亏推,那Mybatis的動(dòng)態(tài)代理和JDK的動(dòng)態(tài)代理有什么區(qū)別学赛?(這個(gè)就是面試很常問(wèn)的一個(gè)問(wèn)題)
JDK代理.png

用過(guò)JDK動(dòng)態(tài)代理的都知道,在實(shí)現(xiàn)了 InvocationHandler 的代理類(lèi)里面吞杭,需要傳入一個(gè)被代理對(duì)象的實(shí)現(xiàn)類(lèi)盏浇。

Mybatis動(dòng)態(tài)代理.png

這里 不需要實(shí)現(xiàn)是因?yàn)椋何覀冎恍枰鶕?jù)接口類(lèi)型+方法的名稱(chēng),就可以找到Statement ID了芽狗,而唯一要做的一件事情也是這件绢掰,所以不需要實(shí)現(xiàn)類(lèi)。在MapperProxy里面直接執(zhí)行邏輯(也就是執(zhí)行 SQL)就可以童擎。

小結(jié)

獲得 Mapper 對(duì)象的過(guò)程滴劲,實(shí)質(zhì)上是獲取了一個(gè) MapperProxy 的代理對(duì)象。MapperProxy 中有 sqlSession顾复、mapperInterface班挖、methodCache。

執(zhí)行sql

上面提到的Mapper都是MapperProxy代理對(duì)象捕透,所以所有的方法都值執(zhí)行invoker方法

1.Mapper.invoke()

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //首先判斷是否需要去執(zhí)行 SQL,還是直接執(zhí)行方法碴萧。
      //Object 本身的方法和 Java 8 中接口的默認(rèn)方法不需要去執(zhí)行SQL
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //獲取緩存,保存了方法簽名和接口方法的關(guān)系
    //為了提升 MapperMethod 的獲取速度
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

2.MapperMethod.execute()

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()) {
          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;
  }

MapperMethod里面主要有兩個(gè)參數(shù) ,一 個(gè)是SqlCommand 乙嘀, 一 個(gè) 是MethodSignature,這兩個(gè)都是 MapperMethod 的內(nèi)部類(lèi)破喻。

在execute方法中虎谢,根據(jù)不同的type和返回類(lèi)型:
調(diào)用 convertArgsToSqlCommandParam()將參數(shù)轉(zhuǎn)換為 SQL 的參數(shù)。
調(diào)用 sqlSession 的 insert()曹质、update()婴噩、delete()擎场、selectOne ()方法
以查詢(xún)?yōu)槔瑫?huì)走到 selectOne()方法几莽。

3.DefaultSqlSession.selectOne()
sqlSession.selectOne(command.getName(), param)最終調(diào)用了selectList()方法

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;
    }
  }

在 SelectList()中迅办,我們先根據(jù) command name(Statement ID)從 Configuration中拿到 MappedStatement,這個(gè) ms 上面有我們?cè)?xml 中配置的所有屬性章蚣,包括 id站欺、statementType、sqlSource纤垂、useCache矾策、入?yún)ⅰ⒊鰠⒌鹊惹吐佟?zhí)行了 Executor 的 query()方法贾虽。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      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();
    }
  }

3.1.執(zhí)行器的選擇
除了SIMPLE/REUSE/BATCH,還有一種包裝類(lèi)型CachingExecutor吼鱼。

如果啟用了二級(jí)緩存蓬豁,就會(huì)先調(diào)用 CachingExecutor 的 query()方法,里面有緩存相關(guān)的操作蛉抓,然后才是再調(diào)用基本類(lèi)型的執(zhí)行器庆尘。

在沒(méi)有開(kāi)啟二級(jí)緩存的情況下,先會(huì)走到 BaseExecutor 的 query()方法(否則會(huì)先走到 CachingExecutor)巷送。

4.BaseExecutor.query()
4.1.創(chuàng)建CacheKey
從 Configuration 中獲取 MappedStatement驶忌, 然后從 BoundSql 中獲取 SQL 信息,創(chuàng)建 CacheKey笑跛。這個(gè) CacheKey 就是緩存的 Key付魔。
然后再調(diào)用另一個(gè) query()方法。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

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;
  }

4.2.清空本地緩存
queryStack 用于記錄查詢(xún)棧飞蹂,防止遞歸查詢(xún)重復(fù)處理緩存几苍。

flushCache=true 的時(shí)候,會(huì)先清理本地緩存(一級(jí)緩存):clearLocalCache();

如果沒(méi)有緩存陈哑,會(huì)從數(shù)據(jù)庫(kù)查詢(xún):queryFromDatabase()

如果 LocalCacheScope == STATEMENT妻坝,會(huì)清理本地緩存。

4.3.從數(shù)據(jù)庫(kù)查詢(xún)
4.3.1.緩存
先在緩存用占位符占位惊窖。執(zhí)行查詢(xún)后刽宪,移除占位符,放入數(shù)據(jù)界酒。

4.3.2.查詢(xún)
執(zhí)行 Executor 的 doQuery()圣拄;默認(rèn)是 SimpleExecutor。

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.");
    }
    //flushCache=true 的時(shí)候毁欣,會(huì)先清理本地緩存(一級(jí)緩存)
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //用于記錄查詢(xún)棧庇谆,防止遞歸查詢(xún)重復(fù)處理緩存
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //如果沒(méi)有緩存岳掐,會(huì)從數(shù)據(jù)庫(kù)查詢(xún)
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      //如果 LocalCacheScope == STATEMENT,會(huì)清理本地緩存饭耳。
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

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;
  }

5.SimpleExecutor.doQuery()

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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

5.1.創(chuàng)建StatementHandler
在 configuration.newStatementHandler()中串述,new 一個(gè)StatementHandler,先得到 RoutingStatementHandler哥攘。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

RoutingStatementHandler 里 面 沒(méi) 有 任 何 的 實(shí) 現(xiàn) 剖煌, 是 用 來(lái) 創(chuàng) 建 基 本 的StatementHandler 的。這里會(huì)根據(jù) MappedStatement 里面的 statementType 決定StatementHandler 的 類(lèi) 型 逝淹。 默 認(rèn) 是 PREPARED ( STATEMENT 耕姊、 PREPARED 、CALLABLE)栅葡。

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());
    }
  }

StatementHandler 里面包含了處理參數(shù)的 ParameterHandler 和處理結(jié)果集的ResultSetHandler茉兰。

這2個(gè)對(duì)象是在這里創(chuàng)建的

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

這三個(gè)對(duì)象都是可以被插件攔截的四大對(duì)象之一,所以在創(chuàng)建之后都要用攔截器進(jìn)行包裝的方法欣簇。

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

5.2.創(chuàng)建Statement
用 new 出來(lái)的 StatementHandler 創(chuàng)建 Statement 對(duì)象的prepareStatement()方法對(duì)語(yǔ)句進(jìn)行預(yù)編譯规脸,處理參數(shù)。
handler.query(stmt, resultHandler);

5.3.執(zhí)行StatementHandler的query()方法
RoutingStatementHandler 的 query()方法熊咽,delegate 委派莫鸭,最終執(zhí)行 PreparedStatementHandler 的 query()方法。

5.4.執(zhí)行PreparedStatement 的 execute() 方法
JDBC 包中的 PreparedStatement 的執(zhí)行了横殴。

5.5.ResultSetHandler處理結(jié)果集
resultSetHandler.handleResultSets(ps);
根據(jù)代碼可知:我們會(huì)先拿到第一個(gè)結(jié)果集被因,如果沒(méi)有配置一個(gè)查詢(xún)返回多個(gè)結(jié)果集的情況,一般只有一個(gè)結(jié)果集衫仑。如果下面的這個(gè) while 循環(huán)我們也不用梨与,就是執(zhí)行一次。然后會(huì)調(diào)用 handleResultSet()方法文狱。

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

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

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    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);
  }

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

源碼核心對(duì)象總結(jié)

對(duì)象 關(guān)聯(lián)對(duì)象 作用
Configuration MapperRegistry TypeAliasRegistry TypeHandlerRegistry 包含了 MyBatis 的所有的配置信息
SqlSession SqlSessionFactory DefaultSqlSession 對(duì)操作數(shù)據(jù)庫(kù)的增刪改查的 API 進(jìn)行了封裝粥鞋,提供給應(yīng)用層使用
Executor BaseExecutor SimpleExecutor BatchExecutor ReuseExecutor MyBatis 執(zhí)行器,是 MyBatis 調(diào)度的核心瞄崇,負(fù)責(zé) SQL 語(yǔ)句的生成和查詢(xún)緩存的維護(hù)
StatementHandler BaseStatementHandler SimpleStatementHandler PreparedStatementHandler CallableStatementHandler 封裝了 JDBC Statement 操作呻粹,負(fù)責(zé)對(duì) JDBC statement 的操作,如設(shè)置參數(shù)苏研、將 Statement 結(jié)果集轉(zhuǎn)換成 List 集合
ParameterHandler DefaultParameterHandler 把用戶(hù)傳遞的參數(shù)轉(zhuǎn)換成 JDBC Statement 所需要的參數(shù)
ResultSetHandler DefaultResultSetHandler 把 JDBC 返回的 ResultSet 結(jié)果集對(duì)象轉(zhuǎn)換成 List 類(lèi)型的集合
MapperProxy MapperProxyFactory 代理對(duì)象等浊,用于代理 Mapper 接口方法
MappedStatement SqlSource BoundSql MappedStatement 維護(hù)了一條<select、update楣富、delete凿掂、insert>節(jié)點(diǎn)的封裝伴榔,包括了 SQL 信息纹蝴、入?yún)⑿畔⒆⒊鰠⑿畔?/td>
Mybatis創(chuàng)建會(huì)話工廠類(lèi).png
Mybatis創(chuàng)建會(huì)話.png
Mybatis調(diào)用代理對(duì)象方法,執(zhí)行SQL.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末塘安,一起剝皮案震驚了整個(gè)濱河市糠涛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兼犯,老刑警劉巖忍捡,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異切黔,居然都是意外死亡砸脊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)纬霞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凌埂,“玉大人,你說(shuō)我怎么就攤上這事诗芜⊥ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵伏恐,是天一觀的道長(zhǎng)孩哑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)翠桦,這世上最難降的妖魔是什么横蜒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮秤掌,結(jié)果婚禮上愁铺,老公的妹妹穿的比我還像新娘。我一直安慰自己闻鉴,他們只是感情好茵乱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著孟岛,像睡著了一般瓶竭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渠羞,一...
    開(kāi)封第一講書(shū)人閱讀 49,919評(píng)論 1 290
  • 那天斤贰,我揣著相機(jī)與錄音,去河邊找鬼次询。 笑死荧恍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播送巡,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摹菠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了骗爆?” 一聲冷哼從身側(cè)響起次氨,我...
    開(kāi)封第一講書(shū)人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摘投,沒(méi)想到半個(gè)月后煮寡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犀呼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年幸撕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片外臂。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杈帐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出专钉,到底是詐尸還是另有隱情挑童,我是刑警寧澤,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布跃须,位于F島的核電站站叼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏菇民。R本人自食惡果不足惜尽楔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望第练。 院中可真熱鬧阔馋,春花似錦、人聲如沸娇掏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)婴梧。三九已至下梢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間塞蹭,已是汗流浹背孽江。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留番电,地道東北人岗屏。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親这刷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涎跨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容