我们使用的是上一期创建的Spring boot项目,没看过那篇文章的可以去看看Springboot整合数据库 +JpaRepository实现简单数据查询
Redis 是目前业界使用最广泛的内存数据存储。相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化。
本文介绍 Spring boot 集成 Redis 来实现数据缓存
在pom
文件中添加redis常用依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml
配置Redis redis:
#数据库索引
database: 0
#服务器地址
host: 127.0.0.1
#端口号
port: 6379
#redis密码(默认为空)
password:
#连接redis超时时间
timeout: 3000
在application.yml
文件中配置了redis相关配置了之后呢,我们就可以在使用的类上直接引入RedisTemplate
,具体怎么引入看下述代码
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
我这里就在Service实现类上引入了RedisTemplate
第一个问题:存进value
的Object
,必须序列化要不然会报错
序列化的话在User
类上实现Serializable
接口即可,看下图
第二个问题:存进redis
的key
也需要序列化增加其可读性,我们先来看看不序列化的key
上述的代码执行步骤呢就是先去数据库查询名称为userInfoKey
的key
,然后如果查到了直接返回结果即可,没查到就去数据库查询,并把查询到的结果放到缓存里,再返回结果
执行完上面的步骤,在redis里的key
应该是userInfoKey
,但是并不是这样的,我们去redis里看看
我框出来的这段就是我们存进redis的key
可以观察到,它是一个序列化后的字符串,这样当key
比较多的时候,是非常不方便我们观察的,所以我们在往redis里插入数据时,就得设置key的序列化对象为字符串,加上下面这段代码即可
redisTemplate.setKeySerializer(new StringRedisSerializer());
将redis现在的key
删除,执行完上面的代码,我们再来看看redis中的key
是怎么样的
所以,当我们设置了key
的序列化属性后,存进redis里的就是我们设置的key
,在数据库中也更方便我们的观察
第三个问题:所生成的key必须是不同的查询返回的结果所对应的key必须是不同的,如果像我们上面那样写出来的代码,无论我查询name = "张三"
还是name = "李四"
返回的结果是一样的,因为我们key
是写死的,所以针对不同的查询,我们必须生成不同的key,例如根据类名称、方法名称、包名称和方法参数来生成一个唯一的key
,这样就可以根据不同的查询所生成对应的key
去缓存查询不同的结果
上面说了这么多,其实直接使用RedisTemplate
还是有很多问题的,一般我们都会使用注解的方式+RedisConfig 来定义贴合我们项目的缓存
针对上面的问题,spring boot引入的redis的包,里面包含了有关redis的相关注解,我们来看看
我给大家解释一下这个@AliasFor
注解,它的意思是别名的意思,看着上面的图片,也就是key
和value
互为别名,也就是说@AliasFor
必须成对出现,且@AliasFor
标注的字段必须设置默认值,要不然会报错
@Cacheable
可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的
// @Cacheable(value = "'userInfo' + #p0")
// @Cacheable(value = "userInfo",key = "#p0")
//参数为user的话key可以使用这个属性获取到user内的属性
//尽量使用数据的唯一变量,不能保证user.name是唯一的
// @Cacheable(value = "userInfo",key = "#user.name")
// @Cacheable(value = "userInfo",key = "#p0.name")
public User getUserInfo(String name) {
return userRepository.findByName(name);
}
我们来看看redis中生成的key
就是我们通过了传进来的参数作为key,保证了每次传进来的参数不同,查询缓存返回的结果不同,这都是Redis内部封装好的key生成策略,我们也可以自定义key的生成策略
我们来重点看看这个condition
@Cacheable(value = "userInfo",key = "#p0",condition = "#p0.equals('张三')")
public User getUserInfo(String name) {
return userRepository.findByName(name);
}
这个字段就是为我们做了一些过滤的操作,也就是说,加上这个字段,就可以写一些表达式,向上面这段代码,当我们传进来的参数为张三
时才会进行缓存,传进其它的参数不进行缓存,这里可以使用SpringEL表达式来尽行操作
还有一些其他的属性,具体我不展开,感兴趣的在下面评论我们交流交流
这个注解其实就是类似清除缓存的一个触发器,当它标注在类上则说明该类的方法只要执行,都会触发清除缓存的操作,可以指定清除缓存的key(value)
以及使用condition
来匹配清除什么样的key
这里说一下allEntries
和beforeInvocation
allEntries
表示是否需要清除缓存中的所有元素,属性为boolean
,若为true
则无视key(value)
所设置的值,表示清除所有的元素,默认为false
beforeInvocation
表示是在方法执行之前清除缓存还是执行之后清除缓存,若为true
则表示方法执行前清楚缓存,默认为false,即方法执行成功后才清除缓存,若方法执行中抛出异常没执行完则不会清除缓存
@CachePut不会检查缓存中的数据,而是每次都会把方法执行完,把返回的结果存到指定缓存中
@Cacheable会去缓存查,如果缓存有,直接从缓存中取出来返回
这个注解是可以同时设置多组**@Cacheable、@CachePut和@CacheEvict**
首先我们要明白RedisConfig
中需要包含什么,首先看看我们直接使用RedisTemplate
的问题,我们就知道RedisConfig
要包含什么了,我们在RedisConfig
需要规定好根据不同的查询生成的key
,key
和value
的序列化和反序列化
我们在RedisConfig
中配置的自定义方法,最终通过注解引用的就是我们自定义的方法
因为key在redis通常都是String类型的,我们定义一个将key序列化成String的一个类
实现其RedisSerializer
接口,主要重写的方法是序列化的过程,因为key不一定是String类型的,还有可能是json类型,如果key为json我们先将key,转化为String类型,再序列化,防止序列化出现问题
public class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
private final String target = "\"";
private final String replacement = "";
public StringRedisSerializer() {
this(Charset.forName("UTF8"));
}
public StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
//
@Override
public byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (string == null) {
return null;
}
string = string.replace(target, replacement);
return string.getBytes(charset);
}
}
与原Redis默认的value序列化方法不同的是,我们选择的是FastJson来value进行序列化,而默认采用Jackson2Json来尽行序列化
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
PS:关于FastJson有很多争议,具体感兴趣的朋友可以去查查看,生产环境下不推荐使用fastjson,自定义JacksonJson也可以
最后的RedisConfig.class
,继承了CachingConfigurerSupport
类,重写了KeyGenerator
方法,也就是key的生成策略
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
/**
* 设置 redis 数据默认过期时间,默认1天
* 设置@cacheable 序列化方式
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
//设置key的时效性,一般为一天就过期
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(1));
//最后返回我们的配置类
return configuration;
}
//忽略所有类型的警告
@SuppressWarnings("all")
//spring管理的一个bean,使用@Autowired注入到使用的类即可
@Bean(name = "redisTemplate")
//注册相同类型的bean,就不会成功,保持当前只有一个名为"redisTemplate"的bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer,也就是上面我们编写的fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// 全局开启AutoType,不建议使用
//ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 建议使用这种方式,小范围指定白名单
ParserConfig.getGlobalInstance().addAccept("com.hecl.zhenghe.domain");
// key的序列化采用StringRedisSerializer,也就是我们刚刚编写的StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 自定义缓存key生成策略
* 使用方法 @Cacheable(keyGenerator="keyGenerator")
* @return
*/
@Bean
@Override
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder stringBuilder = new StringBuilder();
//加入类名
stringBuilder.append(target.getClass().getName());
//加入方法名
stringBuilder.append(method.getName());
//加入包名
stringBuilder.append(target.getClass().getPackage());
//加入查询参数
for (Object obj : params) {
stringBuilder.append(JSON.toJSONString(obj).hashCode());
}
//最终我们生成的key就是这四种的结合,这就能保证我们生成key的唯一性
return stringBuilder.toString();
};
}
}
key的过期时间通过Duration
设置还可以设置多少小时、分钟过期,下图为该类的部分方法
当我们重写完上边的方法之后,redis就会从默认使用的序列化反序列化、key的生成策略等都会使用我们自定义的方式了,当然还可以重写当缓存报错时的处理方式,重写下列红框的方法即可
其实编写RedisConfig方法就是对一些现有的Redis方法进行重写 ,让Redis使用我们重写的方法,使其更贴合我们的项目,特别是一些高并发的项目更要注重这些细节
转载请标注原作者,谢谢你们的支持,能给个小心心吗?
完成:2021/4/08 22:38 ALiangXLogic