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)分層和模塊劃分
接口層
接口層是最重要的一層律杠,核心對(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ì)象決定的胰挑。
主要做的事情:
- 把接口中傳入的參數(shù)解析并且映射成 JDBC 類(lèi)型蔓罚;
- 解析 xml 文件中的 SQL 語(yǔ)句,包括插入?yún)?shù)瞻颂,和動(dòng)態(tài) SQL 的生成豺谈;
- 執(zhí)行 SQL 語(yǔ)句;
- 處理結(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)啟的拘鞋,不需要任何的配置。
如果要在同一個(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)
開(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)題)
用過(guò)JDK動(dòng)態(tài)代理的都知道,在實(shí)現(xiàn)了 InvocationHandler 的代理類(lèi)里面吞杭,需要傳入一個(gè)被代理對(duì)象的實(shí)現(xiàn)類(lèi)盏浇。
這里 不需要實(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> |