作者:ChenZhen
博客地址:https://www.chenzhen.space/
版权:本文为博主 ChenZhen 的原创文章,本文版权归作者所有,转载请附上原文出处链接及本声明。
如果对你有帮助,请给一个小小的star⭐
最近在学习Redis,这个是我做的笔记,另外推荐Redis的学习视频,比硅谷讲的更详细,而且实战很多)
黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+redis实战
引入相关依赖
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis
官网地址:https://spring.io/projects/spring-data-redis
-
提供了对不同Redis客户端的整合(Lettuce和Jedis)
-
提供了RedisTemplate统一API来操作Redis
-
支持Redis的发布订阅模型
-
支持Redis哨兵和Redis集群
-
支持基于Lettuce的响应式编程
-
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
-
支持基于Redis的JDKCollection实现
引入下面的依赖
<!-- spring boot整合redis场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
配置文件
spring:
redis:
host: 192.168.43.128 #Redis服务器的Ip
port: 6379
#password: 123456
lettuce:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100 # 连接等待时间
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
测试
package com.chenzhen.springboot_redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("age", "年龄");
Object age = redisTemplate.opsForValue().get("age");
System.out.println("age = " + age);
}
}
成功获取到值
序列化问题
当你以为已经成功利用springboot操作Redis的时候
但是当你查询Redis数据库会发现在Redis中存入的键值对和你输入的键值对并不一样
开头都加上了一堆乱码,这是为什么呢?
原因是springboot提供的操控Redis的方法的形参都是Object类型,需要经过序列化成为字符串才能存入Redis,而如果不指定那么会使用默认的jdk的序列化器,就会出现问题
这个时候呢我们需要配置一个配置类,指定springboot的序列化方式
直接复制即可
package com.chenzhen.springboot_redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author ChenZhen
* @Description
* @create 2022/10/6 20:15
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
//变双冒号为单冒号
.computePrefixWith(name -> name+":")
//设置过期时间600秒
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
在这里还需要引入一个依赖,这样才不会报错
这个依赖已经在springboot的web模块里面有了,但是这边我没有引入web的启动器,所以我单独引入这个依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
重新设置key-value’
查看redis数据库,发现已经成功存入,并且没有乱码
而且存入对象时会自动转化成json格式保存到redis数据库中
并且获取的时候,会自动将json格式转换为java对象
其实到这里已经可以结束了,但是尽管JSON的序列化方式可以满足我们的需求,却依然存在一些问题,如图:
为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。
当需要存储Java对象时,手动完成对象的序列化和反序列化。
StringRedisTemplate
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
如果你使用的是手动序列化的方法,则之前配置的RedisTemplate就不需要了
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testStringTemplate() throws JsonProcessingException {
// 准备对象
User user = new User("虎哥", 18);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入一条数据到redis
stringRedisTemplate.opsForValue().set("user:200", json);
// 读取数据
String val = stringRedisTemplate.opsForValue().get("user:200");
// 反序列化
User user1 = mapper.readValue(val, User.class);
System.out.println("user1 = " + user1);
}
你可以直接编写springboot提供的API来操作缓存,当然你也可以使用更方便的注解方式。
使用注解操作
缓存介绍
Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。
其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中。
@EnableCaching开启缓存注解的使用
1、开启基于注解的缓存,使用 @EnableCaching 标识在 SpringBoot 的主启动类上。
@SpringBootApplication
@EnableCaching//开启基于缓存的注解
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
(1)@Cacheable
根据方法对其返回值进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不
存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
属性值如下:
-
cacheNames / value(必填):用来指定缓存组件的名字,value和cacheNames作用一样,二者必须选一个,可以是数组的方式,支持指定多个缓存。
-
key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)
-
keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用
-
cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。当我们添加进redis相关的启动器之后, SpringBoot会使用RedisCacheConfigratioin 当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是RedisCacheManager , 这个缓存管理器创建的Cache为 RedisCache , 进而操控redis进行数据的缓存
-
condition :可以用来指定符合条件的情况下才缓存
-
unless :否定缓存,与condition相对,当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
-
sync :是否使用异步模式。默认是方法执行完,以同步的方式将方法返回的结果存在缓存中。
一个小案例,这里注意必须将注解标注方法的类交给spring管理,也就是加上要@Bean、@Service等标签,否则会失效
这里的#id就是spEL表达式,代表从方法的形参取id的值
直接编写测试方法运行
成功将返回值写入缓存。
小知识点:redis key 键名称中的冒号——命名空间层次的表示
redis中key的命名,用 冒号:分隔不同的层次命名空间,如:userId:12345,表示ID为12345的用户
如果表示某个对象的字段,也用冒号分隔,如:userId:123456:name,表示ID为12345的用户名
如果某个对象有字段的字段,用.连接。如user:id12345:friend.id。表示ID为12345的用户的朋友的ID
spEL 编写 key
前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据:
@Cacheable(value=“user”,key=“123”) 这个注释的意思是,当调用这个方法的时候,会从缓存中查询键为user:123的值,如果没有,则执行实际的方法(即查询数据库),并将方法的返回结果存入缓存中。如果找到了,则直接返回缓存中的值,不会执行实际的方法(即查询数据库)
(2)@CachePut
和 @Cacheable 不同的是,使用该注解标志的方法,它每次都会触发真实方法的调用,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取数据,而不需要再去查询数据库。一般用在新增方法上。
@CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新
大部分属性值与Cacheable类似
(3)@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上,大部分属性值与*@Cacheable*相似,
- allEntries:清空所有缓存,默认为false
- beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
(4)@Caching
有时候会遇到一个方法需要多个注解的时候,我们可以用**@Caching**这个注解,同时设置多个缓存的信息设置,它能够为一个方法提供多个缓存配置
@Caching(
cacheable = {
@Cacheable(value = "name",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email"),
}
)
@Caching(evict = {
@CacheEvict(value = "pageIndex",allEntries = true),
@CacheEvict(value = "recommendList",allEntries = true),
@CacheEvict(value = "blog",key = "#blog.id")
})