目录
一、在spring boot 环境整合
https://blog.youkuaiyun.com/xushiyu1996818/article/details/89036631
二、Mybatis映射文件使用二级缓存
mapper级标签
<!--
eviction LRU
flushInterval缓存时间,以毫秒为单位
size缓存大小
readOnly如果为false的话,缓存对象必须是可序列化的-->
<cache eviction="LRU"
type="com.xusy.util.MybatisCacheRedisMap"
flushInterval="120000"
size="1024"
readOnly="true"/>
开启二级缓存,在XML配置文件中添加Cache节点即可,
eviction为缓存的策略有, 默认的是 LRU:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
type为使用的redis缓存工具类
flushInterval为缓存时间,以毫秒为单位,此时为120秒
size为保存多少个结果,现在为1024个
readOnly为是否是只读的,现在为true,在不同线程中的调用者之间修改它们会 导致冲突
只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改的,这样性能会好一些,缺点是因为他是只读的,所以不能被修改。如果设置为false的话,读写的缓存会通过序列化返回该缓存对象的拷贝,因为会把对象进行拷贝,这会慢一些,但是安全,因此默认是 false。
cache-ref标签
<cache-ref namespace="mapper.StudentMapper"/>
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。(这个当语句涉及联表操作要用到)
语句级
一旦在mapper里面写了cache标签,整个mapper的所有语句,都会默认使用缓存,默认方式如下
当为select语句时:
flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
useCache默认为true,表示会将本条语句的结果进行二级缓存。
当为insert、update、delete语句时:
flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。
useCache属性在该情况下没有。
当为select语句的时候,如果没有去配置flushCache、useCache,那么默认是启用缓存的,所以,如果有必要,那么就需要人工修改配置,修改结果类似下面:
<select id="save" parameterType="XX" flushCache="true" useCache="false">
……
</select>
update 的时候如果 flushCache="false",则当你更新后,查询的数据数据还是老的数据。
三、Redis缓存工具类
mybatis二级缓存接口
/*jadclipse*/// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache
{
public abstract String getId();
public abstract void putObject(Object obj, Object obj1);
public abstract Object getObject(Object obj);
public abstract Object removeObject(Object obj);
public abstract void clear();
public abstract int getSize();
public abstract ReadWriteLock getReadWriteLock();
}
总共分为7个方法,最重要的是put,get,clear方法
分别会在缓存加入(第一次select),缓存命中(第二次select),缓存清除(insert,update,delete时)
在mapper里的cache标签里的type的属性就是实现了这个cache接口的缓存工具类
redis-string缓存工具类
package com.xusy.util;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
public class MybatisCacheRedisString implements Cache {
private static final Logger logger = LoggerFactory.getLogger(MybatisCacheRedisString.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id;
private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public MybatisCacheRedisString(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
logger.debug("创建了mybatis的redis缓存"+id);
}
@Override
public String getId() {
return id;
}
/**
* Put query result to redis
*
* @param key
* @param value
*/
@Override
@SuppressWarnings("unchecked")
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
//redisTemplate进行value操作(key-string)
ValueOperations opsForValue = redisTemplate.opsForValue();
//进行字符串操作,放入key和value,redis过期时间=EXPIRE_TIME_IN_MINUTES
opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
logger.debug("redis cache "+id+" put key: "+key.toString()+" value: "+value.toString());
}
/**
* Get cached query result from redis
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
logger.debug("redis cache "+id+" get key: "+key.toString());
//得到key的value
return opsForValue.get(key);
}
/**
* Remove cached query result from redis
*
* @param key
* @return
*/
@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
//删除这个key
redisTemplate.delete(key);
logger.debug("redis cache "+id+" remove key: "+key.toString());
return null;
}
/**
* Clears this cache instance
*/
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.execute((RedisCallback) connection -> {
//将数据库清空
connection.flushDb();
return null;
});
logger.debug("Clear all the cached query result from redis "+id);
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
查看一条日志
2019-04-11 16:59:20.597 DEBUG 19980 --- [nio-8080-exec-9] com.xusy.util.MybatisCacheRedisMap : redis cache com.xusy.dao.UserMapper2 put key: -1962129931:1974203225:com.xusy.dao.UserMapper2.findById:0:2147483647:select * from user where id=?:2:SqlSessionFactoryBean value: [ ]
这个mybatisCacheRedisString 缓存工具类有以下几个属性
readwritelock:读写锁,每个工具类实例各一个,并通过接口提供出去
id:工具类实例的标识,上面的例子就是com.xusy.dao.UserMapper2,就是这个mapper的namespace,可以看到不同mapper使用的工具类实例不同
redisTemplate:通过另外一个工具类applicationContextHolder从spring容器中获得。
putObject
//redisTemplate进行value操作(key-string)
ValueOperations opsForValue = redisTemplate.opsForValue();
//进行字符串操作,放入key和value,redis过期时间=EXPIRE_TIME_IN_MINUTES
opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
先得到string操作的模板ValueOperations
然后进行string操作,key=key,value=value
可以看到上面那一次:key=-1962129931:1974203225:com.xusy.dao.UserMapper2.findById:0:2147483647:select * from user where id=?:2:SqlSessionFactoryBean
相当于是mapper的包+方法+sql语句+sql参数
value为[] 相当于是返回的结果,也可以是value: [User [id=4, name=xsy, age=10]]
getObject
从数据库里得到key为key,字符串的结果
Clear
redisTemplate.execute((RedisCallback) connection -> {
//将数据库清空
connection.flushDb();
return null;
});
可以看到这个clear操作将整个redis数据库清空,包括不属于这个namespace的数据,需要改进
redis-HashMap缓存工具类
package com.xusy.util;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
public class MybatisCacheRedisMap implements Cache {
private static final Logger logger = LoggerFactory.getLogger(MybatisCacheRedisMap.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id; // 在构造函数自动注入,为namespace的名字
private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public MybatisCacheRedisMap(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
logger.debug("创建了mybatis的redis缓存"+id);
}
@Override
public String getId() {
return id;
}
/**
* Put query result to redis
*
* @param key
* @param value
*/
@Override
@SuppressWarnings("unchecked")
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
HashOperations opsForHash=redisTemplate.opsForHash();
opsForHash.put(id,key,value);
logger.debug("redis cache "+id+" put key: "+key.toString()+" value: "+value.toString());
}
/**
* Get cached query result from redis
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
HashOperations opsForHash=redisTemplate.opsForHash();
logger.debug("redis cache "+id+" get key: "+key.toString());
return opsForHash.get(id, key);
}
/**
* Remove cached query result from redis
*
* @param key
* @return
*/
@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
logger.debug("redis cache "+id+" remove key: "+key.toString());
HashOperations opsForHash=redisTemplate.opsForHash();
opsForHash.delete(id, key);
return null;
}
/**
* Clears this cache instance
*/
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.execute((RedisCallback) connection -> {
redisTemplate.delete(id);
return null;
});
logger.debug("Clear all the cached query result from redis "+id);
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
putObject
HashOperations opsForHash=redisTemplate.opsForHash();
opsForHash.put(id,key,value);
可以看到这次的put操作,是以id为key(就是namespace为key),数据结构是hashmap,map里的key为key,value为value
getObject
HashOperations opsForHash=redisTemplate.opsForHash();
logger.debug("redis cache "+id+" get key: "+key.toString());
return opsForHash.get(id, key);
这个get操作,是以id为key,取出hashmap中key=key的value
clear
redisTemplate.delete(id);
在mybatis缓存工具类中hashmap优于string的原因就是这个clear操作,string的clear是将数据库清空,而hashmap的clear是将key为id的value删除,不会删除其他namespace(key为其他id)的缓存数据
四、使用Mybatis二级缓存的注意事项
在一个命名空间下使用二级缓存
二级缓存对于不同的命名空间namespace的数据是互不干扰的,倘若多个namespace中对一个表进行操作的话,就会导致这不同的namespace中的数据不一致的情况。
在单表上使用二级缓存
在做关联关系查询时,就会发生多表的操作,此时有可能这些表存在于多个namespace中,这就会出现上一条内容出现的问题了。
查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。