Mybatis緩存通過(guò)org.apache.ibatis.cache.Cache實(shí)現(xiàn),利用Redis做Mybatis的二級(jí)緩存需要實(shí)現(xiàn)這個(gè)接口。
緩存過(guò)程中產(chǎn)生的key注意存儲(chǔ),flushCache的時(shí)候要?jiǎng)h除這些key對(duì)應(yīng)的緩存值。這里把這些key保存到為redis的list結(jié)構(gòu),以id作為list的key,每個(gè)mapper產(chǎn)生的id不變。
- 使用
redisTemplate操作redis,可以參考文檔:Spring集成shiro使用redis管理session - 不需要專門(mén)為保存key建立一個(gè)redis.list,直接使用
redisTemplate.keys(key+"*")就可以,同樣參考文檔:Spring集成shiro使用redis管理session
MybatisRedisCache.java
package com.sdhs.mob.common.redis;
import org.apache.ibatis.cache.Cache;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* mybatis redis 二級(jí)緩存
*
* @author seer
* @date 2018/3/13 14:17
*/
public class MybatisRedisCache implements Cache {
private static Logger LOGGER = LogManager.getLogger(MybatisRedisCache.class);
private final String id;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static JedisConnectionFactory jedisConnectionFactory;
/**
* 這個(gè)地方需要靜態(tài)注入,這里通過(guò)中間類 MybatisRedisCacheTransfer 實(shí)現(xiàn)的
* @param jedisConnectionFactory
*/
public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
MybatisRedisCache.jedisConnectionFactory = jedisConnectionFactory;
}
public MybatisRedisCache(final String id) {
if (null == id || "".equals(id)) {
throw new IllegalArgumentException("mybatis redis cache need an id.");
}
this.id = id;
LOGGER.debug("mybatis redis cache id: {}", id);
}
@Override
public String getId() {
return this.id;
}
/**
* 存值
* @param key
* @param value
*/
@Override
public void putObject(Object key, Object value) {
if (null == key) {
return;
}
LOGGER.debug("mybatis redis cache put. K={} value={}", key, value);
RedisConnection redisConnection = null;
try {
redisConnection = jedisConnectionFactory.getConnection();
RedisSerializer serializer = new JdkSerializationRedisSerializer();
redisConnection.set(serializer.serialize(key), serializer.serialize(value));
// 將key保存到redis.list中
redisConnection.lPush(serializer.serialize(id), serializer.serialize(key));
} catch (Exception e) {
LOGGER.error("mybatis redis cache put exception. K=" + key + " V=" + value + "", e);
} finally {
if (null != redisConnection) {
redisConnection.close();
}
}
}
/**
* 取值
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
if (null == key) {
return null;
}
LOGGER.debug("mybatis redis cache get. K={}", key);
RedisConnection redisConnection = null;
Object result = null;
try {
redisConnection = jedisConnectionFactory.getConnection();
RedisSerializer serializer = new JdkSerializationRedisSerializer();
result = serializer.deserialize(redisConnection.get(serializer.serialize(key)));
} catch (Exception e) {
LOGGER.error("mybatis redis cache get exception. K=" + key + " V=" + result + "", e);
} finally {
if (null != redisConnection) {
redisConnection.close();
}
}
return result;
}
/**
* 刪值
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
if (null == key) {
return null;
}
LOGGER.debug("mybatis redis cache remove. K={}", key);
RedisConnection redisConnection = null;
Object result = null;
try {
redisConnection = jedisConnectionFactory.getConnection();
RedisSerializer serializer = new JdkSerializationRedisSerializer();
// 講key設(shè)置為立即過(guò)期
result = redisConnection.expireAt(serializer.serialize(key), 0);
// 將key從redis.list中刪除
redisConnection.lRem(serializer.serialize(id), 0, serializer.serialize(key));
} catch (Exception e) {
LOGGER.error("mybatis redis cache remove exception. " + key + " V=" + result + "", e);
} finally {
if (null != redisConnection) {
redisConnection.close();
}
}
return result;
}
/**
* 清空緩存
* flushCache="true" 的時(shí)候會(huì)調(diào)用這個(gè)地方
*/
@Override
public void clear() {
LOGGER.debug("mybatis redis cache clear. ");
RedisConnection redisConnection = null;
try {
redisConnection = jedisConnectionFactory.getConnection();
/**
* 千萬(wàn)不要直接 redisConnection.flushDb(),會(huì)把整個(gè)redis的東西都清除掉,我不相信你的redis里沒(méi)有其他東西
* 獲取redis.list中的保存的key值,遍歷刪除
*/
Long length = redisConnection.lLen(serializer.serialize(id));
if (0 == length) {
return;
}
List<byte[]> keyList = redisConnection.lRange(serializer.serialize(id), 0, length - 1);
for (byte[] key : keyList) {
redisConnection.expireAt(key, 0);
}
redisConnection.expireAt(serializer.serialize(id), 0);
keyList.clear();
} catch (Exception e) {
LOGGER.error("mybatis redis cache clear exception. ", e);
} finally {
if (null != redisConnection) {
redisConnection.close();
}
}
}
@Override
public int getSize() {
int result = 0;
try {
RedisConnection redisConnection = jedisConnectionFactory.getConnection();
result = Math.toIntExact(redisConnection.dbSize());
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}
類中采用了JdkSerializationRedisSerializer序列化Redis的key,幾種序列化方式比較可以參照:關(guān)于Spring Data redis幾種對(duì)象序列化的比較
clear()的時(shí)候千萬(wàn)不要直接 redisConnection.flushDb(),會(huì)把整個(gè)redis的東西都清除掉。
MybatisRedisCacheTransfer.java
package com.sdhs.mob.common.redis;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
/**
* mybatis redis 二級(jí)緩存參數(shù)傳遞
*
* @author seer
* @date 2018/3/13 14:17
*/
public class MybatisRedisCacheTransfer {
public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
MybatisRedisCache.setJedisConnectionFactory(jedisConnectionFactory);
}
}
<bean class="com.sdhs.mob.common.redis.MybatisRedisCacheTransfer">
<property name="jedisConnectionFactory" ref="jedisConnectionFactory"/>
</bean>
對(duì)MybatisRedisCache.java中的jedisConnectionFactory注入傳遞
Mybatis的配置和使用
mybatis-config.xml
<configuration>
<settings>
<!-- 啟動(dòng)二級(jí)緩存 -->
<setting name="cacheEnabled" value="true" />
</settings>
</configuration>
在mapper.xml中使用
<mapper namespace="com.sdhs.mapper.AddressMapper">
<!-- 配置緩存類 -->
<cache type="com.sdhs.mob.common.redis.MybatisRedisCache"/>
<resultMap id="baseResultMap" type="xxx">
</resultMap>
<!--
flushCache="true"
insert、delete、update的時(shí)候清空緩存
-->
<insert id="save"
parameterType="xxx"
useGeneratedKeys="true"
flushCache="true"
keyProperty="addrId">
INSERT INTO Address
<set>
<include refid="setSql"/>
</set>
</insert>
<!--
useCache="true"
select的時(shí)候使用緩存
-->
<select id="list"
parameterType="xxx"
resultMap="baseResultMap"
useCache="true">
SELECT
<include refid="colSql"/>
FROM es_address
<where>
<include refid="whereSql"/>
</where>
ORDER BY modifyDate desc
<include refid="limitSql"/>
</select>
<mapper>
Test的時(shí)候可以觀察下兩次相同查詢時(shí)日志打印,查詢和修改時(shí)Redis里key的變化。
參考文檔:spring+springmvc+mybatis+redis實(shí)現(xiàn)緩存 - 甘雨路 - 博客園 感謝作者