MyBatis緩存介紹
正如大多數(shù)持久層框架一樣翎朱,MyBatis 同樣提供了一級(jí)緩存和二級(jí)緩存的支持
- 一級(jí)緩存基于PerpetualCache 的 HashMap本地緩存者疤,其存儲(chǔ)作用域?yàn)?Session,當(dāng) Session flush 或 close 之后,該Session中的所有 Cache 就將清空。
- 二級(jí)緩存與一級(jí)緩存其機(jī)制相同吩抓,默認(rèn)也是采用 PerpetualCache歧焦,HashMap存儲(chǔ)肠套,不同在于其存儲(chǔ)作用域?yàn)?Mapper(Namespace)入宦,并且可自定義存儲(chǔ)源巢钓,如 Ehcache破婆。
- 對(duì)于緩存數(shù)據(jù)更新機(jī)制狡门,當(dāng)某一個(gè)作用域(一級(jí)緩存Session/二級(jí)緩存Namespaces)的進(jìn)行了 Create/Update/Delete 操作后,默認(rèn)該作用域下所有 select 中的緩存將被clear锅很。
Mybatis的一級(jí)緩存
映射文件
<mapper namespace="com.shxt.dao.UserDao">
<resultMap type="com.shxt.model.User" id="BaseResultMapper">
<id column="user_id" property="user_id"/>
<result column="account" property="account"/>
<result column="password" property="password"/>
<result column="user_name" property="user_name"/>
<result column="status" property="status"/>
<result column="login_time" property="login_time"/>
<result column="ip" property="ip"/>
<result column="fk_role_id" property="fk_role_id"/>
</resultMap>
<sql id="sys_user_columns">
user_id,account,password,user_name,status,login_time,ip,fk_role_id
</sql>
<select id="load" parameterType="int" resultMap="BaseResultMapper">
SELECT
<include refid="sys_user_columns"/>
FROM
sys_user
WHERE user_id=#{user_id}
</select>
</mapper>
查詢(xún) | 一級(jí)緩存測(cè)試
@Test
public void 查詢(xún)_一級(jí)緩存測(cè)試(){
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
User u1 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("第一次查詢(xún):"+u1);
User u2 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("第二次查詢(xún):"+u2);
} finally {
MyBatisUtils.closeSqlSession(sqlSession);
}
}
控制臺(tái)運(yùn)行結(jié)果說(shuō)明
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 1, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
第一次查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
第二次查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
從以上結(jié)果中可以看出其馏,兩次調(diào)用load方法,但是只有一次查詢(xún)數(shù)據(jù)庫(kù)的過(guò)程爆安,這種現(xiàn)象產(chǎn)生的原因就是mybatis的一級(jí)緩存叛复,并且一級(jí)緩存是默認(rèn)開(kāi)啟的。
查詢(xún)-變更-查詢(xún) | 一級(jí)緩存測(cè)試
@Test
public void 查詢(xún)_變更_一級(jí)緩存測(cè)試(){
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
User u1 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("第一次查詢(xún):"+u1);
User u2 = new User();
u2.setUser_id(-999);
u2.setStatus(2);
//變更數(shù)據(jù)庫(kù)
sqlSession.update(UserDao.class.getName()+".update", u2);
User u3 = sqlSession.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("第二次查詢(xún):"+u3);
//這里一定要提交扔仓,不然數(shù)據(jù)進(jìn)不去數(shù)據(jù)庫(kù)中
sqlSession.commit();
}catch (Exception ex) {
ex.printStackTrace();
}finally {
MyBatisUtils.closeSqlSession(sqlSession);
}
}
如果中間過(guò)程中涉及到CUD操作,那么緩存自動(dòng)消失,重新查詢(xún)
控制臺(tái)運(yùn)行結(jié)果說(shuō)明
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 1, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
第一次查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=1, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
DEBUG [main] - ==> Preparing: UPDATE sys_user SET status = ? WHERE user_id=?
DEBUG [main] - ==> Parameters: 2(Integer), -999(Integer)
DEBUG [main] - <== Updates: 1
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
第二次查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
Mybatis的二級(jí)緩存
默認(rèn)情況下是沒(méi)有開(kāi)啟Mybatis二級(jí)緩存的,那么我測(cè)試一下如下代碼,看看運(yùn)行效果
@Test
public void 查詢(xún)_沒(méi)有二級(jí)緩存測(cè)試(){
//第一個(gè)SqlSession
SqlSession sqlSession1 = null;
//第二個(gè)SqlSession
SqlSession sqlSession2 = null;
try {
sqlSession1 = MyBatisUtils.getSqlSession();
sqlSession2 = MyBatisUtils.getSqlSession();
User u1 = sqlSession1.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("sqlSession1 查詢(xún):"+u1);
User u2 = sqlSession2.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("sqlSession2 查詢(xún):"+u2);
} finally {
MyBatisUtils.closeSqlSession(sqlSession1);
MyBatisUtils.closeSqlSession(sqlSession2);
}
}
控制臺(tái)運(yùn)行結(jié)果說(shuō)明
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
sqlSession1 查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
Tue Sep 05 16:20:42 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
sqlSession2 查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
兩個(gè)session褐奥,分別查詢(xún)id為-999 的 User ,那么mybatis與數(shù)據(jù)庫(kù)交互了兩次翘簇,這樣說(shuō)明mybatis現(xiàn)在沒(méi)有開(kāi)啟二級(jí)緩存撬码,需要我們手動(dòng)的開(kāi)啟。
第一步:序列化持久化類(lèi)
User.java類(lèi)去實(shí)現(xiàn)Serializable接口,不然MyBatis的二級(jí)緩存不好用
public class User implements java.io.Serializable{
private static final long serialVersionUID = 1L;
}
第二步:手動(dòng)開(kāi)啟二級(jí)緩存
這里的原則是版保,如果開(kāi)啟了二級(jí)緩存呜笑,那么在關(guān)閉sqlsession后,會(huì)把該sqlsession一級(jí)緩存中的數(shù)據(jù)添加到namespace的二級(jí)緩存中彻犁。
mybatis-config.xml配置信息
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
cacheEnabled的默認(rèn)值就是true,所以可以忽略
UserMapper.xml開(kāi)始二級(jí)緩存標(biāo)簽
<mapper namespace="com.shxt.dao.UserDao">
<!-- 開(kāi)啟二級(jí)緩存 -->
<cache></cache>
<!-- 省略的部分代碼 -->
</mapper>
第三步:測(cè)試代碼
@Test
public void 查詢(xún)_二級(jí)緩存測(cè)試() {
// 第一個(gè)SqlSession
SqlSession sqlSession1 = null;
// 第二個(gè)SqlSession
SqlSession sqlSession2 = null;
sqlSession1 = MyBatisUtils.getSqlSession();
sqlSession2 = MyBatisUtils.getSqlSession();
User u1 = sqlSession1.selectOne(UserDao.class.getName() + ".load", -999);
System.out.println("sqlSession1 查詢(xún):" + u1);
MyBatisUtils.closeSqlSession(sqlSession1);// 關(guān)閉
User u2 = sqlSession2.selectOne(UserDao.class.getName() + ".load", -999);
System.out.println("sqlSession2 查詢(xún):" + u2);
MyBatisUtils.closeSqlSession(sqlSession2);
}
控制臺(tái)運(yùn)行結(jié)果說(shuō)明
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.0
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
sqlSession1 查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.5
sqlSession2 查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
在默認(rèn)情況下叫胁,當(dāng)sqlsession執(zhí)行commit后會(huì)刷新緩存,這樣的寫(xiě)法等價(jià)上面的寫(xiě)法
@Test
public void 查詢(xún)_二級(jí)緩存測(cè)試(){
//第一個(gè)SqlSession
SqlSession sqlSession1 = null;
//第二個(gè)SqlSession
SqlSession sqlSession2 = null;
try {
sqlSession1 = MyBatisUtils.getSqlSession();
sqlSession2 = MyBatisUtils.getSqlSession();
User u1 = sqlSession1.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("sqlSession1 查詢(xún):"+u1);
sqlSession1.commit();//強(qiáng)制刷新
User u2 = sqlSession2.selectOne(UserDao.class.getName()+".load",-999);
System.out.println("sqlSession2 查詢(xún):"+u2);
} finally {
MyBatisUtils.closeSqlSession(sqlSession1);
MyBatisUtils.closeSqlSession(sqlSession2);
}
}
當(dāng)為select語(yǔ)句時(shí):
- flushCache默認(rèn)為false,表示任何時(shí)候語(yǔ)句被調(diào)用汞幢,都不會(huì)去清空本地緩存和二級(jí)緩存驼鹅。
- useCache默認(rèn)為true,表示會(huì)將本條語(yǔ)句的結(jié)果進(jìn)行二級(jí)緩存森篷。
修改映射文件如下
<mapper namespace="com.shxt.dao.UserDao">
<!-- 開(kāi)啟二級(jí)緩存 -->
<cache></cache>
<resultMap type="com.shxt.model.User" id="BaseResultMapper">
<id column="user_id" property="user_id"/>
<result column="account" property="account"/>
<result column="password" property="password"/>
<result column="user_name" property="user_name"/>
<result column="status" property="status"/>
<result column="login_time" property="login_time"/>
<result column="ip" property="ip"/>
<result column="fk_role_id" property="fk_role_id"/>
</resultMap>
<sql id="sys_user_columns">
user_id,account,password,user_name,status,login_time,ip,fk_role_id
</sql>
<!--
flushCache="true" 強(qiáng)制刷新
-->
<select id="load" parameterType="int" resultMap="BaseResultMapper" flushCache="true">
SELECT
<include refid="sys_user_columns"/>
FROM
sys_user
WHERE user_id=#{user_id}
</select>
</mapper>
再次運(yùn)行之前commit的提交的測(cè)試方法 , 運(yùn)行結(jié)果為:
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.0
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
sqlSession1 查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
DEBUG [main] - Cache Hit Ratio [com.shxt.dao.UserDao]: 0.5
DEBUG [main] - ==> Preparing: SELECT user_id,account,password,user_name,status,login_time,ip,fk_role_id FROM sys_user WHERE user_id=?
DEBUG [main] - ==> Parameters: -999(Integer)
TRACE [main] - <== Columns: user_id, account, password, user_name, status, login_time, ip, fk_role_id
TRACE [main] - <== Row: -999, super, super, 劉文銘, 2, 2017-07-30 09:50:18.0, , -100
DEBUG [main] - <== Total: 1
sqlSession2 查詢(xún):User [user_id=-999, account=super, password=super, user_name=劉文銘, status=2, login_time=Sun Jul 30 09:50:18 CST 2017, ip=, fk_role_id=-100]
當(dāng)為insert输钩、update、delete語(yǔ)句時(shí):
- flushCache默認(rèn)為true疾宏,表示任何時(shí)候語(yǔ)句被調(diào)用张足,都會(huì)導(dǎo)致本地緩存和二級(jí)緩存被清空触创。
- useCache屬性在該情況下沒(méi)有
總結(jié)
一級(jí)緩存
- 默認(rèn)開(kāi)啟
- 必須同一個(gè)session坎藐,如果session對(duì)象已經(jīng)close()過(guò)了就不能用了
- 查詢(xún)條件必須一致
- 沒(méi)有執(zhí)行過(guò)session.cleanCache();清理緩存
- 沒(méi)有執(zhí)行過(guò)增刪改操作(這些操作都會(huì)清理緩存)
二級(jí)緩存
1.mybatis-config.xml 中默認(rèn)配置
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
2.必須手動(dòng)開(kāi)啟在Mapper.xml中添加
<mapper namespace="com.shxt.dao.UserDao">
<!-- 開(kāi)啟二級(jí)緩存 -->
<cache></cache>
<resultMap type="com.shxt.model.User" id="BaseResultMapper">
<id column="user_id" property="user_id"/>
<result column="account" property="account"/>
<result column="password" property="password"/>
<result column="user_name" property="user_name"/>
<result column="status" property="status"/>
<result column="login_time" property="login_time"/>
<result column="ip" property="ip"/>
<result column="fk_role_id" property="fk_role_id"/>
</resultMap>
<sql id="sys_user_columns">
user_id,account,password,user_name,status,login_time,ip,fk_role_id
</sql>
<!--
flushCache="true" 強(qiáng)制刷新
-->
<select id="load" parameterType="int" resultMap="BaseResultMapper">
SELECT
<include refid="sys_user_columns"/>
FROM
sys_user
WHERE user_id=#{user_id}
</select>
</mapper>
3.映射語(yǔ)句文件中的所有select語(yǔ)句將會(huì)被緩存。
4.映射語(yǔ)句文件中的所有insert哼绑,update和delete語(yǔ)句會(huì)刷新緩存岩馍。
5.緩存會(huì)使用Least Recently Used(LRU,最近最少使用的)算法來(lái)收回抖韩。
6.緩存會(huì)根據(jù)指定的時(shí)間間隔來(lái)刷新蛀恩。
7.緩存會(huì)存儲(chǔ)1024個(gè)對(duì)象
<cache
eviction="FIFO" //回收策略為先進(jìn)先出
flushInterval="60000" //自動(dòng)刷新時(shí)間60s
size="512" //最多緩存512個(gè)引用對(duì)象
readOnly="true"/> //只讀
說(shuō)在后面的話,個(gè)人感覺(jué)MyBatis的緩存意義不大:
A. 面對(duì)一定規(guī)模的數(shù)據(jù)量,內(nèi)置的cache方式就派不上用場(chǎng)了;
B. 對(duì)查詢(xún)結(jié)果集做緩存并不是MyBatis框架擅長(zhǎng)的茂浮,它專(zhuān)心做的應(yīng)該是sql mapper双谆。采用此框架的Application去構(gòu)建緩存更合理壳咕,比如采用OSCache、Memcached啥的顽馋。