一、Mybatis 缓存机制
1、一级缓存:
√ \color{#FF7D00}{√} √ 默认开启,存在于 SqlSession 的生命周期中,Mybatis 会把执行的方法和参数通过算法生成缓存的键值对,并和查询结果存入一个 map 中,如果同一个 SqlSession 中执行的方法和参数完全一致时,通过算法生成相同的键值对,并在 map 中查找发现已存在,则会返回缓存中的对象。任何 insert、update、delete 操作都会清空一级缓存。
√
\color{#FF7D00}{√}
√ 一般的我们将 Mybatis 和 Spring 整合时,mybatis-spring 包会自动分装 sqlSession,而 Spring通过动态代理 sqlSessionProxy 使用一个模板方法封装了 select() 等操作,每一次 select() 查询都会自动先执行 openSession(), 执行完 close() 以后调用 close() 方法,相当于生成了一个新的 session 实例,所以我们无需手动的去关闭这个 session(),当然也无 法使用 mybatis 的一级缓存,也就是说 mybatis 的一级缓存在 spring 中是没有作用的。
2、二级缓存:
√ \color{#FF7D00}{√} √ 二级缓存存在于 SqlSessionFactory 的生命周期中,可以理解为跨 SqlSession 的,缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。
√ \color{#FF7D00}{√} √ setting 参数 cacheEnabled,这个参数是二级缓存的全局开关,默认值是 true,如果把这个参数设置为 false,即使有后面的二级缓存配置,也不会生效。
√ \color{#FF7D00}{√} √ 要开启二级缓存,需要在 sql 映射文件中添加配置:
<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true" />
● \color{#FF7D00}{●} ● 映射语句文件中的所有 select 语句会被缓存;
● \color{#FF7D00}{●} ● 映射语句文件中的所有 insert、update、delete 语句会刷新缓存;
● \color{#FF7D00}{●} ● 缓存会使用 LRU (最近最少使用)算法来回收;
● \color{#FF7D00}{●} ● 60000ms 刷新间隔,存储 1024 个引用数目(不论查询返回什么);
● \color{#FF7D00}{●} ● 可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false 。
√
\color{#FF7D00}{√}
√ 在集群环境下,使用自带的二级缓存只是针对单个的节 点,所以我们采用分布式的二级缓存功能(redis)。
二、Redis 实现 Mybatis 的二级缓存
2、实现 cache 接口
public enum CacheManager {
/**
* singleton instance
*/
INSTANCE;
private final ConcurrentHashMap<String, RedisCache> cacheMap = new ConcurrentHashMap<>(64);
/**
* register redis cache obj
*
* @param redisCache
* @return
*/
public RedisCache register(RedisCache redisCache) {
return this.cacheMap.put(redisCache.getId(), redisCache);
}
}
public class RedisCache implements Cache {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisCache.class);
/**
* 读写锁
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 缓存实例id
*/
private final String id;
private RedisTemplate redisTemplate;
/**
* 缓存过期时间,目前为6个小时
*/
private static final long EXPIRE_TIME_IN_MINUTES = 60 * 6;
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
LOGGER.debug("Redis Cache id " + id);
this.id = id;
CacheManager.INSTANCE.register(this);
}
@Override
public String getId() {
return id;
}
/**
* Put query result to redis
*
* @param key
* @param value
*/
@Override
public void putObject(Object key, Object value) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
opsForValue.set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
LOGGER.debug("Put query result to redis");
} catch (Throwable t) {
LOGGER.error("Redis put failed", t);
}
}
/**
* Get cached query result from redis
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
Object o = opsForValue.get(key.toString());
if (o == null) {
LOGGER.debug("Redis get failed, fail over to db");
} else {
LOGGER.debug("Get cached query result from redis,key:" + key + " value:" + o.toString());
}
return o;
} catch (Throwable t) {
LOGGER.error("Redis get failed, fail over to db", t);
return null;
}
}
/**
* Remove cached query result from redis
*
* @param key
* @return
*/
@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key.toString());
LOGGER.debug("Remove cached query result from redis");
} catch (Throwable t) {
LOGGER.error("Redis remove failed", t);
}
return null;
}
/**
* Clears this cache instance
*/
@Override
public void clear() {
try {
String pattern = "*:" + this.id + "*";
delete(pattern);
} catch (Exception e) {
LOGGER.error("clear:{}", e.getMessage(), e);
}
LOGGER.info("Clear all the cached query result from redis");
}
public boolean delete(String pattern) {
RedisCallback<Boolean> redisCallback = connection -> {
try {
ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions();
scanOptionsBuilder.match(pattern);
ScanOptions scanOptions = scanOptionsBuilder.build();
Cursor<byte[]> cursor = connection.scan(scanOptions);
while (cursor.hasNext()) {
connection.del(cursor.next());
}
return true;
} catch (Exception e) {
return false;
}
};
Object result = getRedisTemplate().execute(redisCallback);
if (result != null) {
return (boolean) result;
}
return false;
}
/**
* This method is not used
*
* @return
*/
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
synchronized (this) {
if (redisTemplate == null) {
redisTemplate = (RedisTemplate) SpringUtil.getBean("dbRedisTemplate");
}
}
}
return redisTemplate;
}
}
/**
* 重写redis序列化,支持数据库二级缓存,配置ObjectMapper,
*
* @author zilong
**/
@Configuration(value = "DbMapperRedisConfig")
@EnableCaching // 开启缓存支持
public class DbCacheConfig extends CachingConfigurerSupport {
/**
* RedisTemplate配置(数据库版)
*
* @param lettuceConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> dbRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 序列化配置
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 缓存配置管理器
*
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
// 配置序列化(缓存默认有效期 6小时)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(6));
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 以锁写入的方式创建RedisCacheWriter对象
//RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
// 创建默认缓存配置对象
/* 默认配置,设置缓存有效期 1小时*/
//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
/* 自定义配置test:demo 的超时时间为 5分钟*/
RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration)
.withInitialCacheConfigurations(singletonMap(CacheConstant.CSYJ_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)).disableCachingNullValues()))
.transactionAware().build();
return cacheManager;
}
}
2、Mapper 开启二级缓存配置
√ \color{#FF7D00}{√} √ @CacheNamespace
● \color{#FF7D00}{●} ● 作用于 mapper 接口上面,是用来实现二级缓存的,如果不指定实现类的话,默认就是 PerpetualCache(实际上就是hashmap实现的)。
● \color{#FF7D00}{●} ● 只适用于注解或者 tk.mapper,对 xml 无效。并且不能和 xml 搭配用。
√ \color{#FF7D00}{√} √ @CacheNamespaceRef
● \color{#FF7D00}{●} ● 可以和 xml 搭配使用。需要指明 name 或者 value 。
● \color{#FF7D00}{●} ● Xml配置缓存
<cache type=”xxx.MybatisCache”></cache>
√ \color{#FF7D00}{√} √ @Options(useCache = false)
● \color{#FF7D00}{●} ● 个别方法不使用缓存
代码地址: h t t p s : / / g i t e e . c o m / z i l o n g 123666 / c a b i n e t . g i t \color{red}{代码地址:https://gitee.com/zilong123666/cabinet.git} 代码地址:https://gitee.com/zilong123666/cabinet.git