1 Mybatis緩存
1.1 一級(jí)緩存
一級(jí)緩存Mybatis
的一級(jí)緩存是指SQLSession
,一級(jí)緩存的作用域是SQLSession, Mabits默認(rèn)開(kāi)啟一級(jí)緩存
意述。
在同一個(gè)SqlSession
中幸冻,執(zhí)行相同的SQL
查詢(xún)時(shí)务傲;第一次會(huì)去查詢(xún)數(shù)據(jù)庫(kù)舔株,并寫(xiě)在緩存中首启,第二次會(huì)直接從緩存中取房铭。
當(dāng)執(zhí)行SQL
時(shí)候兩次查詢(xún)中間發(fā)生了增刪改
的操作驻龟,則SQLSession
的緩存會(huì)被清空。
每次查詢(xún)會(huì)先去緩存中找缸匪,如果找不到翁狐,再去數(shù)據(jù)庫(kù)查詢(xún),然后把結(jié)果寫(xiě)到緩存中凌蔬。
Mybatis
的內(nèi)部緩存使用一個(gè)HashMap
露懒,key
為hashcode+statementId+sql
語(yǔ)句闯冷。Value
為查詢(xún)出來(lái)的結(jié)果集映射成的java對(duì)象
SqlSession
執(zhí)行insert
、update
隐锭、delete
等操作commit
后會(huì)清空該SQLSession
緩存
一級(jí)緩存只是相對(duì)于同一個(gè)SqlSession
而言窃躲。所以在參數(shù)和SQL
完全一樣的情況下,我們使用同一個(gè)SqlSession
對(duì)象調(diào)用一個(gè)Mapper
方法钦睡,往往只執(zhí)行一次SQL
蒂窒,因?yàn)槭褂?code>SelSession第一次查詢(xún)后,MyBatis
會(huì)將其放在緩存中荞怒,以后再查詢(xún)的時(shí)候洒琢,如果沒(méi)有聲明需要刷新,并且緩存沒(méi)有超時(shí)的情況下褐桌,SqlSession
都會(huì)取出當(dāng)前緩存的數(shù)據(jù)衰抑,而不會(huì)再次發(fā)送SQL到數(shù)據(jù)庫(kù)
1.1.1 一級(jí)緩存的生命周期
MyBatis
在開(kāi)啟一個(gè)數(shù)據(jù)庫(kù)會(huì)話(huà)時(shí),會(huì) 創(chuàng)建一個(gè)新的SqlSession
對(duì)象荧嵌,SqlSession
對(duì)象中會(huì)有一個(gè)新的Executor
對(duì)象
Executor
對(duì)象中持有一個(gè)新的PerpetualCache
對(duì)象呛踊;當(dāng)會(huì)話(huà)結(jié)束時(shí),SqlSession
對(duì)象及其內(nèi)部的Executor
對(duì)象還有PerpetualCache
對(duì)象也一并釋放掉
如果SqlSession
調(diào)用了close()
方法啦撮,會(huì)釋放掉一級(jí)緩存PerpetualCache
對(duì)象谭网,一級(jí)緩存將不可用
如果SqlSession
調(diào)用了clearCache()
,會(huì)清空PerpetualCache
對(duì)象中的數(shù)據(jù)赃春,但是該對(duì)象仍可使用
如果SqlSession
中執(zhí)行了任何一個(gè)update
操作(update()愉择、delete()、insert()
) 织中,都會(huì)清空PerpetualCache
對(duì)象的數(shù)據(jù)锥涕,但是該對(duì)象可以繼續(xù)使用
一級(jí)緩存最多緩存 1024
條 SQL
1.1.2 怎么判斷某兩次查詢(xún)是完全相同的查詢(xún)
mybatis
認(rèn)為,對(duì)于兩次查詢(xún)狭吼,如果以下條件都完全一樣层坠,那么就認(rèn)為它們是完全相同的兩次查詢(xún):
- 傳入的
statementId
- 查詢(xún)時(shí)要求的結(jié)果集中的結(jié)果范圍
- 這次查詢(xún)所產(chǎn)生的最終要傳遞給
JDBC java.sql.Preparedstatement的Sql
語(yǔ)句字符串(boundSql.getSql() ) - 傳遞給
java.sql.Statement
要設(shè)置的參數(shù)值
1.1.3 Springboot集成時(shí)一級(jí)緩存不生效問(wèn)題
因?yàn)橐患?jí)緩存是會(huì)話(huà)
級(jí)別的,要生效的話(huà)刁笙,必須要在同一個(gè) SqlSession
中破花。但是與 springboot
集成的 mybatis
,默認(rèn)每次執(zhí)行 sql
語(yǔ)句時(shí)采盒,都會(huì)創(chuàng)建一個(gè)新的 SqlSession
旧乞,所以一級(jí)緩存才沒(méi)有生效蔚润。
當(dāng)調(diào)用 mapper
的方法時(shí)磅氨,最終會(huì)執(zhí)行到 SqlSessionUtils
的 getSqlSession
方法,在這個(gè)方法中會(huì)嘗試在事務(wù)管理器中獲取 SqlSession
嫡纠,如果沒(méi)有開(kāi)啟事務(wù)烦租,那么就會(huì) new 一個(gè) DefaultSqlSession
所以可以猜測(cè)只要將方法開(kāi)啟事務(wù)延赌,那么一級(jí)緩存就會(huì)生效,加上 @Transactional
注解
那么為什么加了@Transactional
注解就可以了呢叉橱,看源碼解析:
看看源碼中是什么時(shí)候?qū)?SqlSession
設(shè)置到事務(wù)管理器中的挫以。
SqlSessionUtils
中,在獲取到 SqlSession
后窃祝,會(huì)調(diào)用 registerSessionHolder
方法注冊(cè) SessionHolder
到事務(wù)管理器:
具體是在 TransactionSynchronizationManager
的 bindResource
方法中操作的掐松,將 SessionHolder
保存到線(xiàn)程本地變量(ThreadLocal) resources
中,這是每個(gè)線(xiàn)程獨(dú)享的粪小。
然后在下次查詢(xún)時(shí)大磺,就可以從這里取出此 SqlSession
,使用同一個(gè) SqlSession
查詢(xún)探膊,一級(jí)緩存就生效了杠愧。
所以基本原理就是:如果當(dāng)前線(xiàn)程存在事物,并且存在相關(guān)會(huì)話(huà)逞壁,就從 ThreadLocal
中取出流济。如果沒(méi)有事務(wù),就重新創(chuàng)建一個(gè) SqlSession
并存儲(chǔ)到 ThreadLocal
當(dāng)中腌闯,共下次查詢(xún)使用绳瘟。
至于緩存查詢(xún)數(shù)據(jù)的地方,是在 BaseExecutor
中的 queryFromDatabase
方法中绑嘹。執(zhí)行 doQuery
從數(shù)據(jù)庫(kù)中查詢(xún)數(shù)據(jù)后稽荧,會(huì)立馬緩存到 localCache(PerpetualCache類(lèi)型)
中:
1.2 二級(jí)緩存
1.2.1 基礎(chǔ)
二級(jí)緩存是mapper級(jí)別
的,Mybatis
默認(rèn)是沒(méi)有開(kāi)啟二級(jí)緩存的工腋,需要在setting全局參數(shù)中配置開(kāi)啟二級(jí)緩存
第一次調(diào)用mapper
下的SQL
去查詢(xún)用戶(hù)的信息姨丈,查詢(xún)到的信息會(huì)存放到該mapper
對(duì)應(yīng)的二級(jí)緩存區(qū)域。 第二次調(diào)用namespace
下的mapper
映射文件中擅腰,相同的sql去查詢(xún)用戶(hù)信息蟋恬,會(huì)去對(duì)應(yīng)的二級(jí)緩存內(nèi)取結(jié)果
二級(jí)緩存是多個(gè)SqlSession
共享的,多個(gè)SqlSession
去操作同一個(gè)Mapper
的sql
語(yǔ)句趁冈,其作用域是mapper
的同一個(gè)namespace
歼争,不同的sqlSession
兩次執(zhí)行相同namespace
下的sql語(yǔ)句
且向sql中傳遞參數(shù)也相同即最終執(zhí)行相同的sql語(yǔ)句,第一次執(zhí)行完畢會(huì)將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)寫(xiě)到緩存(內(nèi)存)渗勘,第二次會(huì)從緩存中獲取數(shù)據(jù)將不再?gòu)臄?shù)據(jù)庫(kù)查詢(xún)沐绒,從而提高查詢(xún)效率
為了更加清楚的描述二級(jí)緩存,先來(lái)看一個(gè)示意圖:
sqlSessionFactory
層面上的二級(jí)緩存默認(rèn)是不開(kāi)啟的旺坠,二級(jí)緩存的開(kāi)啟需要進(jìn)行配置乔遮,實(shí)現(xiàn)二級(jí)緩存的時(shí)候,MyBatis要求返回的POJO必須是可序列化的取刃。 也就是要求實(shí)現(xiàn)Serializable接口蹋肮,配置方法很簡(jiǎn)單出刷,只需要在映射XML文件配置就可以開(kāi)啟緩存了<cache/>
,如果配置了二級(jí)緩存就意味著:
- 映射語(yǔ)句文件中的所有select語(yǔ)句將會(huì)被緩存
- 映射語(yǔ)句文件中的所有insert坯辩、update和delete語(yǔ)句會(huì)刷新緩存
- 緩存會(huì)使用默認(rèn)的Least Recently Used(LRU馁龟,最近最少使用的)算法來(lái)收回
- 根據(jù)時(shí)間表,比如No Flush Interval,(CNFI沒(méi)有刷新間隔)漆魔,緩存不會(huì)以任何時(shí)間順序來(lái)刷新
- 緩存會(huì)存儲(chǔ)列表集合或?qū)ο?無(wú)論查詢(xún)方法返回什么)的1024個(gè)引用
- 緩存會(huì)被視為是read/write(可讀/可寫(xiě))的緩存坷檩,意味著對(duì)象檢索不是共享的,而且可以安全的被調(diào)用者修改改抡,不干擾其他調(diào)用者或線(xiàn)程所做的潛在修改
1.2.2 使用二級(jí)緩存
1.2.2.1 序列化
將po類(lèi)
實(shí)現(xiàn)Serializable
接口
需要將要緩存的pojo
實(shí)現(xiàn)Serializable
接口淌喻,為了將緩存數(shù)據(jù)取出執(zhí)行反序列化操作,因?yàn)槎?jí)緩存數(shù)據(jù)存儲(chǔ)介質(zhì)多種多樣雀摘,不一定只存在內(nèi)存中裸删,有可能存在硬盤(pán)中,如果我們要再取這個(gè)緩存的話(huà)阵赠,就需要反序列化了涯塔。所以建議mybatis
中的pojo都去實(shí)現(xiàn)Serializable
接口
由于二級(jí)緩存的數(shù)據(jù)不一定都是存儲(chǔ)到內(nèi)存中,它的存儲(chǔ)介質(zhì)多種多樣清蚀,所以需要給緩存的對(duì)象執(zhí)行序列化
1.2.2.2 配置緩存
<cache
eviction="FIFO" <!-- 回收策略為先進(jìn)先出 -->
flushInterval="60000" <!--自動(dòng)刷新時(shí)間60s-->
size="512" <!--最多緩存512個(gè)引用對(duì)象-->
readOnly="true"/> <!--只讀-->
在需要開(kāi)啟的namespace
下配置cache標(biāo)簽
匕荸,其中標(biāo)簽中的屬性:
-
eviction:
代表的是緩存回收策略,目前MyBatis
提供以下策略:
LRU:最近最少使用的枷邪,一處最長(zhǎng)時(shí)間不用的對(duì)象
FIFO:先進(jìn)先出榛搔,按對(duì)象進(jìn)入緩存的順序來(lái)移除他們
SOFT:軟引用,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象
WEAK:弱引用东揣,更積極的移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象践惑。這里采用的是LRU,移除最長(zhǎng)時(shí)間不用的對(duì)形象
-
flushInterval
:刷新間隔時(shí)間嘶卧,單位為毫秒尔觉,這里配置的是100
秒刷新,如果你不配置它芥吟,那么當(dāng)SQL被執(zhí)行的時(shí)候才會(huì)去刷新緩存 -
size
:引用數(shù)目侦铜,一個(gè)正整數(shù),代表緩存最多可以存儲(chǔ)多少個(gè)對(duì)象钟鸵,不宜設(shè)置過(guò)大钉稍。設(shè)置過(guò)大會(huì)導(dǎo)致內(nèi)存溢出。這里配置的是1024個(gè)對(duì)象 -
readOnly
:只讀棺耍,意味著緩存數(shù)據(jù)只能讀取而不能修改贡未,這樣設(shè)置的好處是我們可以快速讀取緩存,缺點(diǎn)是我們沒(méi)有辦法修改緩存,他的默認(rèn)值是false
羞秤,即不允許修改
在對(duì)應(yīng)的sql
語(yǔ)句上增加屬性useCache="true"
開(kāi)啟緩存,或者flushCache="true"
刷新緩存
namespace
中配置緩存demo:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yihaomen.mybatis.dao.StudentMapper">
<!--開(kāi)啟本mapper的namespace下的二級(jí)緩存-->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
<resultMap id="studentMap" type="Student">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" />
</resultMap>
<!--可以通過(guò)設(shè)置useCache來(lái)規(guī)定這個(gè)sql是否開(kāi)啟緩存左敌,ture是開(kāi)啟瘾蛋,false是關(guān)閉-->
<select id="selectAllStudents" resultMap="studentMap" useCache="true">
SELECT id, name, age FROM student
</select>
<!--刷新二級(jí)緩存
<select id="selectAllStudents" resultMap="studentMap" flushCache="true">
SELECT id, name, age FROM student
</select>
-->
</mapper>
或者在全局配置中開(kāi)啟緩存開(kāi)關(guān),這樣就把哪些不需要緩存的使用useCache="false"矫限,禁用緩存就可以了
<settings>
<!-- 打開(kāi)延遲加載的開(kāi)關(guān) -->
<setting name = "lazyLoadingEnabled" value = "true"/>
<!-- 把積極加載修改為消極加載哺哼,即延遲加載 -->
<setting name = "aggressiveLazyLoading" value = "false"/>
<!-- 開(kāi)啟二級(jí)緩存 -->
<setting name = "cacheEnabled" value = "true"/>
</settings>
1.2.2.3 SpringBoot使用二級(jí)緩存
在 yaml 中配置 cache-enabled 為 true
mybatis:
configuration:
cache-enabled: true
Mapper
接口上添加 @CacheNamespace
注解:
@CacheNamespace
是 MyBatis
框架中的注解,用于指定命名空間的緩存配置叼风。通過(guò)該注解取董,可以配置該命名空間下的緩存策略,包括緩存類(lèi)型无宿、緩存大小茵汰、緩存過(guò)期時(shí)間等。
使用 @CacheNamespace
注解可以提高 MyBatis
的查詢(xún)效率孽鸡,避免頻繁地訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)蹂午,提高系統(tǒng)的性能。同時(shí)彬碱,也可以通過(guò)該注解來(lái)控制緩存的更新策略豆胸,保證數(shù)據(jù)的一致性。
需要注意的是巷疼,@CacheNamespace
注解只能用于命名空間級(jí)別的緩存配置晚胡,不能用于單個(gè) SQL
語(yǔ)句的緩存配置。如果需要對(duì)單個(gè) SQL
語(yǔ)句進(jìn)行緩存配置嚼沿,可以使用 @Options
注解或在 SQL
語(yǔ)句中使用 <cache>
標(biāo)簽來(lái)實(shí)現(xiàn)估盘。
@CacheNamespace
和 @CacheNamespaceRef
區(qū)別:
-
@CacheNamespace
和@CacheNamespaceRef
都是MyBatis
中用于配置緩存的注解,但是它們的作用和使用方式略有不同骡尽。 -
@CacheNamespace
注解用于標(biāo)注一個(gè)Mapper
接口的緩存配置忿檩,可以配置該Mapper
接口中所有查詢(xún)語(yǔ)句的緩存策略。使用方式如下:
@CacheNamespace(implementation = MybatisRedisCache.class, eviction = MybatisRedisCache.class, flushInterval = 60000, size = 1024)
public interface UserMapper {
// ...
}
其中爆阶,implementation
屬性指定了緩存實(shí)現(xiàn)類(lèi)燥透,eviction
屬性指定了緩存的清除策略,flushInterval
屬性指定了緩存的刷新時(shí)間間隔辨图,size 屬性指定了緩存的最大容量班套。
-
@CacheNamespaceRef
注解用于引用另一個(gè)Mapper
接口的緩存配置,可以將該Mapper
接口的緩存配置與另一個(gè)Mapper
接口的緩存配置共享故河。使用方式如下:
@CacheNamespaceRef(UserMapper.class)
public interface OrderMapper {
// ...
}
其中吱韭,value 屬性指定了被引用的 Mapper 接口。
需要注意的是,@CacheNamespace
和 @CacheNamespaceRef
注解都需要與緩存實(shí)現(xiàn)類(lèi)一起使用理盆,用于指定緩存的具體實(shí)現(xiàn)痘煤。同時(shí),這兩個(gè)注解也可以同時(shí)使用猿规,用于實(shí)現(xiàn)更復(fù)雜的緩存配置衷快。
1.2.3 二級(jí)緩存生效&清除條件
生效的條件:
- 當(dāng)會(huì)話(huà)提交或關(guān)閉之后才會(huì)填充二級(jí)緩存
- 必須是同一個(gè)
mapper
,即同一個(gè)命名空間 - 必須是相同的
statement
姨俩,即同一個(gè)mapper
中的同一個(gè)方法 - 必須是相同的 SQL 語(yǔ)句和參數(shù)
- 如果
readWrite=true(默認(rèn)就是true)
蘸拔,實(shí)體對(duì)像必須實(shí)現(xiàn)Serializable
接口
緩存清除條件:
- 只有修改會(huì)話(huà)提交之后,才會(huì)執(zhí)行清空操作
-
xml
中配置的update
不能清空@CacheNamespace
中的緩存數(shù)據(jù) - 任何一種增刪改操作都會(huì)清空整個(gè)
namespace
中的緩存
1.2.4 源碼中填充二級(jí)緩存
在生效條件中提到了环葵,二級(jí)緩存必須要在會(huì)話(huà)提交或關(guān)閉之后调窍,才能生效
在查詢(xún)到結(jié)果后,會(huì)調(diào)用 SqlSession
的 commit
方法進(jìn)行提交(如果開(kāi)啟事務(wù)的話(huà)张遭,提交 SqlSession
走的不是這里了邓萨,但最終填充二級(jí)緩存的地方是一樣的。):
在此方法中菊卷,最終會(huì)調(diào)用到 TransactionalCache
的 flushPendingEntries
方法中填充二級(jí)緩存:
注意
:springboot
集成 mybatis
的話(huà)先誉,如果沒(méi)有開(kāi)啟事務(wù),每次執(zhí)行查詢(xún)的烁,都會(huì)創(chuàng)建新的 SqlSession
褐耳,所以即使是在同一個(gè)方法中進(jìn)行查詢(xún)操作,那也是跨會(huì)話(huà)的渴庆。
1.2.5 查詢(xún)時(shí)如何使用二級(jí)緩存
在查詢(xún)的時(shí)候铃芦,最終會(huì)調(diào)用 MybatisCachingExecutor
的 query
方法,里面會(huì)從 TransactionalCacheManager
中嘗試根據(jù) key
獲取二級(jí)緩存的內(nèi)容襟雷。
這個(gè) key 很長(zhǎng)刃滓,由 mapper、調(diào)用的查詢(xún)方法耸弄、SQL 等信息拼接而成咧虎,這也是為什么想要二級(jí)緩存生效,必須滿(mǎn)足前面所說(shuō)的條件计呈。
如果能在二級(jí)緩存中查詢(xún)到砰诵,就直接返回了,不需要訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)捌显。
具體的調(diào)用層數(shù)實(shí)在太多茁彭,用到了裝飾者模式,最終是在 PerpetualCache 中獲取緩存的:
打印日志是在 LoggingCache
中:
1.2.6 為什么mybatis默認(rèn)不開(kāi)啟二級(jí)緩存
二級(jí)緩存雖然能帶來(lái)一定的好處扶歪,但是有很大的隱藏危害
它的緩存是以 namespace(mapper)
為單位的理肺,不同 namespace
下的操作互不影響。且 insert/update/delete
操作會(huì)清空所在 namespace
下的全部緩存。
那么問(wèn)題就出來(lái)了妹萨,假設(shè)現(xiàn)在有 ItemMapper
以及 XxxMapper
年枕,在 XxxMapper
中做了表關(guān)聯(lián)查詢(xún),且做了二級(jí)緩存乎完。此時(shí)在 ItemMapper
中將 item
信息給刪了熏兄,由于不同 namespace
下的操作互不影響,XxxMapper
的二級(jí)緩存不會(huì)變囱怕,那之后再次通過(guò) XxxMapper
查詢(xún)的數(shù)據(jù)就不對(duì)了,非常危險(xiǎn)毫别。
來(lái)看一個(gè)例子:
@Mapper
@Repository
@CacheNamespace
public interface XxxMapper {
@Select("select i.id itemId,i.name itemName,p.amount,p.unit_price unitPrice " +
"from item i JOIN payment p on i.id = p.item_id where i.id = #{id}")
List<PaymentVO> getPaymentVO(Long id);
}
@Autowired
private XxxMapper xxxMapper;
@Test
void test() {
System.out.println("==================== 查詢(xún)PaymentVO ====================");
List<PaymentVO> voList = xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList.get(0)));
System.out.println("==================== 更新item表的name ==================== ");
Item item = itemMapper.selectById(1);
item.setName("java并發(fā)編程");
itemMapper.updateById(item);
System.out.println("==================== 重新查詢(xún)PaymentVO ==================== ");
List<PaymentVO> voList2 = xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList2.get(0)));
}
上面的代碼娃弓,test()
方法中前后兩次調(diào)用了 xxxMapper.getPaymentVO
方法,因?yàn)闆](méi)有加 @Transactional
注解岛宦,所以前后兩次查詢(xún)台丛,是兩個(gè)不同的會(huì)話(huà),第一次查詢(xún)完后砾肺,SqlSession
會(huì)自動(dòng) commit
挽霉,所以二級(jí)緩存能夠生效;
然后在中間進(jìn)行了 Item
表的更新操作变汪,修改了下名稱(chēng)侠坎;
由于itemMapper
與 xxxMapper
不是同一個(gè)命名空間,所以 itemMapper
執(zhí)行的更新操作不會(huì)影響到 xxxMapper
的二級(jí)緩存裙盾;
再次調(diào)用 xxxMapper.getPaymentVO
实胸,發(fā)現(xiàn)取出的值是走緩存的,itemName
還是老的番官。但實(shí)際上 itemName
在上面已經(jīng)被改了
1.3 使用Ehcache
點(diǎn)擊此處了解Ehcache原理
Mybatis
本身是一個(gè)持久層框架庐完,它不是專(zhuān)門(mén)的緩存框架,所以它對(duì)緩存的實(shí)現(xiàn)不夠好徘熔,不能支持分布式门躯。
Ehcache
是一個(gè)分布式的緩存框架。
1.3.1 mapper.xml文件中使用
設(shè)置映射文件中cache標(biāo)簽的type值為ehcache的實(shí)現(xiàn)類(lèi)
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<select id="findUserById" parameterType="java.lang.Long" resultType="com.sxt.model.User" flushCache="false">
SELECT * FROM tb_user WHERE id = #{id}
</select>
1.3.2 添加Ehcache的配置文件
在src/main/resources
下創(chuàng)建cache
文件夾酷师,在文件夾下創(chuàng)建ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
defaultCache
標(biāo)簽中屬性:
-
name
:緩存名稱(chēng)讶凉。 -
maxElementsInMemory
:緩存最大個(gè)數(shù)。 -
eternal
:對(duì)象是否永久有效山孔,一但設(shè)置了缀遍,timeout將不起作用。 -
timeToIdleSeconds
:設(shè)置對(duì)象在失效前的允許閑置時(shí)間(單位:秒)饱须。僅當(dāng)eternal=false
對(duì)象不是永久有效時(shí)使用域醇,可選屬性,默認(rèn)值是0,也就是可閑置時(shí)間無(wú)窮大譬挚。 -
timeToLiveSeconds
:設(shè)置對(duì)象在失效前允許存活時(shí)間(單位:秒)锅铅。最大時(shí)間介于創(chuàng)建時(shí)間和失效時(shí)間之間。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用减宣,默認(rèn)是0
盐须,也就是對(duì)象存活時(shí)間無(wú)窮大。 -
overflowToDisk
:當(dāng)內(nèi)存中對(duì)象數(shù)量達(dá)到maxElementsInMemory
時(shí)漆腌,Ehcache
將會(huì)對(duì)象寫(xiě)到磁盤(pán)中贼邓。 -
diskSpoolBufferSizeMB
:這個(gè)參數(shù)設(shè)置DiskStore(磁盤(pán)緩存)的緩存區(qū)大小。默認(rèn)是30MB闷尿。每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū)塑径。 -
maxElementsOnDisk
:硬盤(pán)最大緩存?zhèn)€數(shù)。 -
diskPersistent
:是否緩存虛擬機(jī)重啟期數(shù)據(jù) ,The default value is false. -
diskExpiryThreadIntervalSeconds
:磁盤(pán)失效線(xiàn)程運(yùn)行時(shí)間間隔填具,默認(rèn)是120秒 -
memoryStoreEvictionPolicy
:當(dāng)達(dá)到maxElementsInMemory
限制時(shí)统舀,Ehcache將會(huì)根據(jù)指定的策略去清理內(nèi)存。默認(rèn)策略是LRU
(最近最少使用)劳景。你可以設(shè)置為FIFO
(先進(jìn)先出)或是LFU(較少使用)誉简。 -
clearOnFlush
:內(nèi)存數(shù)量最大時(shí)是否清除
1.4 Mybatis的Executor執(zhí)行器
Mybatis
有三種基本的Executor
執(zhí)行器,SimpleExecutor盟广、ReuseExecutor闷串、BatchExecutor
-
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<String, Statement>
內(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
批處理相同
2 Mybatis攔截器
2.1 攔截器介紹
2.1.1 簡(jiǎn)介
Mybatis
攔截器設(shè)計(jì)的初衷就是為了供用戶(hù)在某些時(shí)候可以實(shí)現(xiàn)自己的邏輯而不必去動(dòng)Mybatis
固有的邏輯。減少代碼侵入
通過(guò)Mybatis
攔截器我們可以攔截某些方法的調(diào)用,我們可以選擇在這些被攔截的方法執(zhí)行前后加上某些邏輯曾棕,也可以在執(zhí)行這些被攔截的方法時(shí)執(zhí)行自己的邏輯而不再執(zhí)行被攔截的方法。所以Mybatis
攔截器的使用范圍是非常廣泛的菜循。
2.1.2 核心概念
MyBatis
攔截器工作時(shí)翘地,核心組件是 Invocation
、Interceptor
癌幕、Method
和 Target
對(duì)象等:
-
Interceptor
:這是所有自定義攔截器的接口衙耕,MyBatis
會(huì)根據(jù)配置找到并調(diào)用實(shí)現(xiàn)該接口的類(lèi)。 -
Invocation
:封裝了方法調(diào)用的對(duì)象勺远,它包含了目標(biāo)方法的信息以及方法的參數(shù)橙喘。通過(guò)Invocation
對(duì)象,我們可以對(duì)方法的執(zhí)行進(jìn)行控制谚中。 -
Method
:表示目標(biāo)方法渴杆,它是通過(guò)反射來(lái)獲取的寥枝。 -
Target
:表示目標(biāo)對(duì)象宪塔,它是被攔截的對(duì)象。例如囊拜,Executor某筐、StatementHandler 等都是目標(biāo)對(duì)象,攔截器會(huì)通過(guò) Target 對(duì)象來(lái)訪(fǎng)問(wèn)和控制這些對(duì)象的行為冠跷。
2.1.3 目標(biāo)對(duì)象和方法
Mybatis
攔截器并不是每個(gè)對(duì)象里面的方法都可以被攔截的南誊。Mybatis攔截器只能攔截Executor、ParameterHandler蜜托、StatementHandler抄囚、ResultSetHandler
四個(gè)對(duì)象里面的方法
默認(rèn)情況下,MyBatis
允許使用插件來(lái)攔截的方法調(diào)用包括:
- 攔截執(zhí)行器方法:
Executor
(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
執(zhí)行 SQL 語(yǔ)句的核心對(duì)象橄务,它的 update()幔托、query() 等方法負(fù)責(zé)執(zhí)行實(shí)際的增、刪蜂挪、改重挑、查操作 - 攔截參數(shù)的處理:
ParameterHandler
(getParameterObject, setParameters)
處理 SQL 參數(shù)綁定的對(duì)象,它負(fù)責(zé)將參數(shù)設(shè)置到 SQL 語(yǔ)句中 - 攔截結(jié)果集的處理:
ResultSetHandler
(handleResultSets, handleOutputParameters)
處理 SQL 查詢(xún)結(jié)果的對(duì)象棠涮,它負(fù)責(zé)將從數(shù)據(jù)庫(kù)返回的ResultSet
轉(zhuǎn)換為Java
對(duì)象谬哀。 - 攔截Sql語(yǔ)法構(gòu)建的處理:
StatementHandler
(prepare, parameterize, batch, update, query)
處理 SQL 語(yǔ)句的對(duì)象,它負(fù)責(zé)將 SQL 語(yǔ)句和參數(shù)綁定严肪,并將其傳遞給數(shù)據(jù)庫(kù)史煎。 - 不同攔截器順序:Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
2.2 Mybatis攔截器接口
public interface Interceptor {
//代理對(duì)象每次調(diào)用的方法谦屑,就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法。在這個(gè)方法里面做我們自定義的邏輯處理
Object intercept(Invocation invocation) throws Throwable;
//plugin方法是攔截器用于封裝目標(biāo)對(duì)象的劲室,通過(guò)該方法我們可以返回目標(biāo)對(duì)象本身伦仍,也可以返回一個(gè)它的代理,
//當(dāng)返回的是代理的時(shí)候我們可以對(duì)其中的方法進(jìn)行攔截來(lái)調(diào)用intercept方法 -- Plugin.wrap(target, this)很洋,
//當(dāng)返回的是當(dāng)前對(duì)象的時(shí)候 就不會(huì)調(diào)用intercept方法充蓝,相當(dāng)于當(dāng)前攔截器無(wú)效
Object plugin(Object target);
//用于在Mybatis配置文件中指定一些屬性的,注冊(cè)當(dāng)前攔截器的時(shí)候可以設(shè)置一些屬性
void setProperties(Properties properties);
}
2.3 @Intercepts注解
Intercepts
注解需要一個(gè)Signature(攔截點(diǎn))參數(shù)數(shù)組喉磁。通過(guò)Signature來(lái)指定攔截哪個(gè)對(duì)象里面的哪個(gè)方法
@Intercepts
注解定義如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
/**
* 定義攔截點(diǎn)
* 只有符合攔截點(diǎn)的條件才會(huì)進(jìn)入到攔截器
*/
Signature[] value();
}
Signature
來(lái)指定咱們需要攔截那個(gè)類(lèi)對(duì)象的哪個(gè)方法谓苟。定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
/**
* 定義攔截的類(lèi) Executor、ParameterHandler协怒、StatementHandler涝焙、ResultSetHandler當(dāng)中的一個(gè)
*/
Class<?> type();
/**
* 在定義攔截類(lèi)的基礎(chǔ)之上,在定義攔截的方法
*/
String method();
/**
* 在定義攔截方法的基礎(chǔ)之上在定義攔截的方法對(duì)應(yīng)的參數(shù)孕暇,
* JAVA里面方法可能重載仑撞,不指定參數(shù),不曉得是那個(gè)方法
*/
Class<?>[] args();
}
我們舉一個(gè)例子來(lái)說(shuō)明妖滔,比如我們自定義一個(gè)MybatisInterceptor類(lèi)隧哮,來(lái)攔截Executor類(lèi)里面的兩個(gè)方法。自定義攔截類(lèi)MybatisInterceptor
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO: 自定義攔截邏輯
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 返回代理類(lèi)
}
@Override
public void setProperties(Properties properties) {
}
}
把源碼知道攔截的主要是如下方法:
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.根據(jù)Statement Id座舍,在mybatis 配置對(duì)象Configuration中查找和配置文件相對(duì)應(yīng)的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//2. 將查詢(xún)?nèi)蝿?wù)委托給MyBatis 的執(zhí)行器 Executor
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}