如何不太优雅的使用Redis缓存
我们都知道使用redis
来缓存我们的数据集合,如下图所示。
通常自己去缓存数据,这样的优点就是逻辑清晰,而且redis
的key
和value
会比较规范。但是冗余代码会比较多,需要自己进行判断数据是否过期。
为了简化业务代码,现在用注解的方式集成redis
二级缓存,但是他的key
和value
就会比较不符合规范。他的key
一共包含5个部分,最重要的就是sql
和这个sql
的参数。他的value
就是这个查询的结果集。
准备工作
引入依赖,mybatis
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.0.6</version>
</dependency>
redis
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置文件
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 这里使用的是 ip:3336/db_order 的数据库(一个服务一个数据库)
url: jdbc:mysql://localhost:3306/sys-common?useUnicode=true&characterEncoding=utf-8&serverTimezone=Hongkong&useSSL=false
username: root
password: 123456
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECt 1
# redis
redis:
host: xxx.xxx.xxx.xxx
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
database: 4
配置redisTemplate
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfiguration {
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用String 序列化
// redisTemplate.setDefaultSerializer(new StringRedisSerializer());
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
注意这里我不使用String
的序列化方式去序列化Key
和Value
实现
实现Cache
接口
package com.laoshiren.hello.redis.cache.mybatis.cache;
import com.laoshiren.hello.redis.cache.mybatis.configure.ApplicationContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class RedisCache implements Cache {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id; // cache instance id
private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.opsForValue().set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
log.debug("Put query result to redis");
} catch (Throwable t) {
log.error("Redis put failed", t);
}
}
@Override
public Object getObject(Object key) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
log.info("Get cached query result from redis");
// System.out.println("****" + opsForValue.get(key).toString());
return redisTemplate.opsForValue().get(key);
} catch (Throwable t) {
log.error("Redis get failed, fail over to db", t);
return null;
}
}
@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete( key.toString());
log.debug("Remove cached query result from redis");
} catch (Throwable t) {
log.error("Redis remove failed", t);
}
return null;
}
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.execute((RedisCallback) connection -> {
connection.flushDb();
return null;
});
log.debug("Clear all the cached query result from redis");
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
给指定的mapper
配置缓存
@CacheNamespace(implementation = RedisCache.class)
public interface TbPostMapper extends BaseMapper<TbPost> {
}
测试
请求一次数据库,使用Debug
模式,它的key
是一个CacheKey
,无法使用使用StringRedisSerializer
去序列化,所以redisTemplate
得使用默认的序列化,即 JdkSerializationRedisSerializer
打开RDM
,看一下4号库。
发现key
和value
就不是很美观,不过不影响使用,当然您可以使用StringRedisSerializer
去实现,只不过我在尝试的过程中获取sql
和参数的时候,会出现一点问题。希望有大佬可以指出。
带参数的sql
特别注意! 在分页缓存的时候,Page
对象的total
必须自己手动查询一次,不然返回给前端的对象里第一次还有总页数,第二次由于走了缓存就不带这个total
,所以必须手动查询一次。
@GetMapping("page/{pageNo}/{pageSize}")
public ResponseResult<IPage<Area>> page(@PathVariable(name = "pageNo")Integer pageNo,
@PathVariable(name = "pageSize") Integer pageSize,
HttpServletRequest request){
IPage<Area> wherePage = new Page<>(pageNo, pageSize);
String word = request.getParameter("wd");
LambdaQueryWrapper<Area> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(word)) {
queryWrapper.like(Area::getAreaName,word);
}
int count = areaService.count(queryWrapper);
IPage<Area> page = areaService.page(wherePage,queryWrapper);
page.setTotal((long)count);
return new ResponseResult<>(CodeStatus.OK,"操作成功",page);
}
后续我也会继续更新这篇博客。好了,最后还是借用大佬的一句话:“不经一番寒彻骨,怎知梅花扑鼻香”。