今天在开发时发现一个奇怪的问题,我手动改完数据库竟然不生效,反复确认环境无误后猜测是缓存的问题,因为是新接手的项目,代码还不熟悉,仔细一看,是开启了二级缓存,并且存入 Redis。
那今天就聊聊怎么优雅的用 Redis 作为 Mybatis 的二级缓存。
要优雅就选择 Mybatis-Plus
关于 Mybatis-Plus 的基础设置就不多做介绍了,只说和二级缓存有关的。
首先在配置文件开启二级缓存:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true # 开启二级缓存
mapper-locations: classpath:*/mapper/*.xml
复制代码
Redis 配置
这部分就是 Redis 的基本用法:
redis:
host: 101.411.160.111
database: 0
port: 6311
password: 1111111
复制代码
配置 RedisTemplate:
@Configuration
public class RedisConfig {
/**
* 设置系列化方式、事务等配置
*/
@Bean
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
{
RedisTemplate<String,Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
复制代码
自定义 Mybatis 缓存
我们只需要实现 Cache 这个接口:
@Slf4j
public class MybatisRedisCache implements Cache {
private static final String COMMON_CACHE_KEY = "mybatis";
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private final RedisTemplate<String, Object> redisTemplate;
private final String nameSpace;
public MybatisRedisCache(String nameSpace) {
if (nameSpace == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
redisTemplate = SpringUtil.getBean("redisTemplate");
this.nameSpace = nameSpace;
}
@Override
public String getId() {
return this.nameSpace;
}
private String getKeys() {
return COMMON_CACHE_KEY + "::" + nameSpace + "::*";
}
private String getKey(Object key) {
return COMMON_CACHE_KEY + "::" + nameSpace + "::" + DigestUtils.md5Hex(String.valueOf(key));
}
@Override
public void putObject(Object key, Object value) {
redisTemplate.opsForValue().set(getKey(key), value, 10, TimeUnit.MINUTES);
}
@Override
public Object getObject(Object key) {
try {
return redisTemplate.opsForValue().get(getKey(key));
} catch (Exception e) {
e.printStackTrace();
log.error("缓存出错 ");
}
return null;
}
@Override
public Object removeObject(Object o) {
Object n = redisTemplate.opsForValue().get(getKey(o));
redisTemplate.delete(getKey(o));
return n;
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys(getKeys());
if (CollectionUtil.isNotEmpty(keys)) {
assert keys != null;
redisTemplate.delete(keys);
}
}
@Override
public int getSize() {
Set<String> keys = redisTemplate.keys(getKeys());
if (CollectionUtil.isNotEmpty(keys)) {
assert keys != null;
return keys.size();
}
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}
复制代码
测试
1.第一次查询,走数据库,并写入缓存:
看看 Redis 的记录:
2.第二次查询,直接走缓存
3.重启项目,依然可以直接查缓存
缓存命中率(Cache Hit Ratio)
不知道有没有细心的同学注意到这样一行日志:
Cache Hit Ratio [com.yitiao.mapper.ArticleMapper]: 0.5
复制代码
最后这个 0.5 就是缓存命中率,代表一共查询两次,命中一次缓存一次。
一级缓存和二级缓存
一级缓存
一级缓存 Mybatis 的一级缓存是指 SQLSession,一级缓存的作用域是 SQlSession , Mabits 默认开启一级缓存。 在同一个 SqlSession 中,执行相同的 SQL 查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行 SQL 时候两次查询中间发生了增删改的操作,则 SQLSession 的缓存会被清空。
每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。 Mybatis 的内部缓存使用一个 HashMap,key 为 hashcode+statementId+sql 语句。Value 为查询出来的结果集映射成的 java 对象。 SqlSession 执行 insert、update、delete 等操作 commit 后会清空该 SQLSession 缓存。
二级缓存
二级缓存 二级缓存是 mapper 级别的,Mybatis 默认是没有开启二级缓存的。 第一次调用 mapper 下的 SQL 去查询用户的信息,查询到的信息会存放到该 mapper 对应的二级缓存区域。 第二次调用 namespace 下的 mapper 映射文件中,相同的 sql 去查询用户信息,会去对应的二级缓存内取结果。
什么时候该开启二级缓存
说实话,我遇到开启二级缓存的时候并不多,因为缓存有利也有弊。
我的建议是如果发现接口耗时严重,可以在线上开启二级缓存,开发环境关掉,为什么呢?
就拿今天我遇到的事来说,开发直接改库不能立即生效,就很烦。
作者:一条 coding
链接:https://juejin.cn/post/7130142146610331661
来源:稀土掘金
评论