目錄
- 什么是二級快取?
- 1. 開啟二級快取
- 如何使用二級快取:
- userCache和flushCache
- 2. 使用Redis實作二級快取
- 如何使用
- 3. Redis二級快取原始碼分析
- 1. 開啟二級快取
什么是二級快取?
二級快取和一級快取的原理是一樣的,第一次查詢,會將資料放入快取中,然后第二次查詢則會直接去快取中取,但是一級快取是基于的sqlSession,而二級快取是基于mapper檔案的namespace的,也就是說多個sqlSession可以共享一個mapper中的二級快取區域,并且如何兩個mapper的namespace相同,即使兩個mapper,那這兩個mapper中執行sql查詢到的資料也將存在相同的二級快取區域中

- 如上圖
sqlSession1在查詢時會從UserMapper的二級快取中取,如果沒有則執行資料庫查詢操作, - 然后寫入到二級快取中
sqlSession2則執行同樣的UserMapper查詢時,會從UserMapper的二級快取中取,此時的二級快取中已經有內容了,所以就可以直接取到,不再與資料庫互動,sqlSession3在執行事務操作(插入、更新、洗掉)時,會清空UserMapper的二級快取
1. 開啟二級快取
如何使用二級快取:
mybatis中,一級快取是默認開啟的,但是二級快取需要配置才可以使用
-
在全域組態檔
sqlMapConfig.xml中加入如下代碼:<!--開啟二級快取--> <settings> <setting name="cacheEnabled" value="https://www.cnblogs.com/isdxh/archive/2020/11/12/true"/> </settings> -
其次在哪個namespace中開啟二級就在哪里配置,因為mybatis有注解和xml兩種方式所以:
-
注解

注解擴展://我們默認使用的是mybatis自帶的二級快取,它的實作在PerpetualCache類中,所以可以寫成 @CacheNamespace(implementation = PerpetualCache.class) //如果是使用redis作為二級快取的話,下面第二部分會講到 -
xml

這樣就開啟了UserMapper的二級快取
-
測驗一:
我們要根據用戶id查詢用戶資訊:

注意:將快取的pojo實作
Serializable介面,為了將快取資料取出執行反序列化操作,因為二級快取的存盤介質多種多樣,不一定只在記憶體中,也可能在硬碟中,如果我們要再取出這個快取的話,就需要反序列化了,所以mybatis的pojo都去實作Serializable介面

最后執行看到列印日志:

為什么System.out.println(user1==user2)為false ?二級快取和一級快取不同,二級快取快取的不是物件,而是資料,在第二次查詢時底層重新創建了一個User物件,并且把二級快取中的資料重新封裝成了物件并回傳,所以user1和user2不是一個物件,
-
測驗二:
我們在測驗二中進行一下事務操作,看看是否能清空二級快取:


? 增加了一個修改操作,發現執行了兩個select,說明提交事務會重繪二級快取
userCache和flushCache
還可以配置userCache和flushCache
-
userCache : 是用來設定是否禁用二級快取的,在statement中設定可以禁用當前select陳述句的二級快取,即每次查詢都會發出sql,默認情況為true.


-
flushCache : 在mapper的同一個namespace中,如果有其它的增刪改操作后需要重繪快取,如果部執行重繪快取會出現臟讀,
設定statement配置中的flushCache="true",即重繪快取,如果改成false則不會重繪,有可能出現臟讀,所以一般情況下沒必要改

Mybatis二級快取和一級快取一樣也是使用到了
org.apache.ibatis.cache.impl.PerpetualCache這個類是mybatis的默認快取類,同時,想要自定義快取必須實作
cache介面

2. 使用Redis實作二級快取
Mybatis自帶的二級快取是有缺點的,就是這個快取是單服務器進行作業的,無法實作分布式快取,

所以為了解決這個問題,必須找一個分布式快取專門存放快取資料,

如何使用
mybatis提供了一個針對cache介面的redis實作類,在mybatis-redis包中
-
首先我們引入jar包
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency> -
修改Mapper.xml檔案
//**********XML方式***********: <?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.lagou.mapper.IUserMapper"> //表示針對于當前的namespace開啟二級快取 <cache type="org.mybatis.caches.redis.RedisCache" /> <select id="findAll" resultType="com.lagou.pojo.User" useCache="true"> select * from user </select>//*******注解方式********** @CacheNamespace(implementation = RedisCache .class) public interface UserMapper { //根據id查詢用戶 注解使用 @Select("select * from user where id=#{id}") public User findById(Integer id);這個類同樣實作了
Cache介面

-
配置redis的組態檔
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0測驗方法同自帶的二級快取一樣,
3. Redis二級快取原始碼分析
RedisCache和Mybatis二級快取的方案都差不多,無非是實作Cache介面,并使用jedis操作快取,不過在設計細節上有點區別,
我們帶著問題分析原始碼:
- 在RedisCache類中如何向redis中進行快取值的存取 ?
- 使用了哪種資料結構 ?
package org.mybatis.caches.redis;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
//首先其實作了Cache介面,被mybatis初始化的時候的CacheBuilder創建
//創建方式就是呼叫了下面的有參構造
public final class RedisCache implements Cache {
private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
private String id;
private static JedisPool pool;
//有參構造
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
//RedisConfigurationBuilder呼叫parseConfiguration()方法創建RedisConfig物件
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
//構建Jedis池
pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),
redisConfig.getDatabase(), redisConfig.getClientName());
}
//模板方法,下面的putObject和getObject、removeObject都會用到這個方法
private Object execute(RedisCallback callback) {
Jedis jedis = pool.getResource();
try {
return callback.doWithRedis(jedis);
} finally {
jedis.close();
}
}
//,,,,,,,,省略部分代碼
@Override
public void putObject(final Object key, final Object value) {
execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
return null;
}
});
}
@Override
public Object getObject(final Object key) {
return execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));
}
});
}
@Override
public Object removeObject(final Object key) {
return execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
return jedis.hdel(id.toString(), key.toString());
}
});
}
}
-
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();

RedisConfig中封裝了默認的Redis配置資訊
這個方法讀取了我們配置在/resource/redis.properties這個檔案
RedisConfig后構建了Jedis池 -
put方法
private Object execute(RedisCallback callback) { Jedis jedis = pool.getResource(); try { return callback.doWithRedis(jedis); } finally { jedis.close(); } } public void putObject(final Object key, final Object value) { execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value)); return null; } }); }我們可以看到,put方法呼叫了模板方法得到 一個jedis鏈接,然后呼叫doWithRedis()方法
jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));可以很清楚的看到,mybatis-redis在存盤資料的時候,是使用的hash結構,把cache的id作為這個hash的key (cache的id在mybatis中就是mapper的namespace);這個mapper中的查詢快取資料作為 hash的field,需要快取的內容直接使用SerializeUtil存盤,SerializeUtil和其他的序列化類差不多,負責物件的序列化和反序列化;
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/212067.html
標籤:其他
