前言
说起Nosql数据库,或者数据库缓存,消息队列,token认证等等应用时,Redis总是大家绕不开的话题。作为一款由C语言开发的运行在内存中进行数据读写的应用,它以高性能和易用性著称。得益于单线程的设计,避免了多线程场景下的锁竞争,从而能做到每秒数十万次的读写操作。同时,Redis还实现了主从模式,哨兵模式和集群模式等分布式解决方案,在保证高性能的前提下提高了可用性和扩展性,成为了开发时不可或缺的组件。
Spring Boot 整合 Redis
由于Spring Boot中各种starter的存在,配置redis其实是简单的事情。
首先在pom中导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
|
spring-boot-starter-data-redis
是redis的spring boot依赖,其中包含了lettuce
客户端。redis的客户端可以选择lettuce
或jedis
,前者是默认的,而且支持异步,在并发场景下的性能更好,推荐使用。
jackson-databind
这个是jackson的依赖,用于之后配置redis的序列化。
commons-pool2
用于配置redis集群。
接下来是redis的config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Configuration public class RedisConfig {
@Bean(name = "myRedisTemplate") public RedisTemplate<String, Object> getRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = getSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate; }
public GenericJackson2JsonRedisSerializer getSerializer() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); return new GenericJackson2JsonRedisSerializer(objectMapper); } }
|
序列化配置
对于Redis自定义序列化,首先要知道为什么需要自定义配置,难道不主动去配置序列化就不能使用了吗?
其实不是,redis已经提供给了我们默认的序列化方法,翻阅官方文档就可以看到如下内容
文中说的意思是JdkSerializationRedisSerializer
是RedisTemplate
和RedisCache
默认的序列化方法。这个默认的序列化方法也就是JDK自带的序列化方法(实现了Serializable接口)。确实如此,如果不进行自定义配置,Redis也是可以用默认的设置进行序列化的。
但是默认方法有一个缺点,当我们将数据存入redis之后,在redis中存储的实际上是序列化之后的格式,也就是无法直接阅读的格式,不利于检查和调试,下面是一个例子。
当我们用默认的序列化向redis中添加键值对(“k1”, “v1”),然后启动redis-cli查看key的是
在redis中存储的是序列化后的形式,前面的一堆16进制是JDK在序列化时添加的一些前缀信息,如果想获得存入时原始的格式,只能通过redisTemplate.opsForValue().get("k1")
来获取。
下面来介绍一下自定义的序列化方法
GenericJackson2JsonRedisSerializer
Jackson2JsonRedisSerializer
这两种序列化方式在序列化String,Object,Collections,JSONObject,JSONArray都完全没有问题,唯一的区别是,当使用GenericJackson2JsonRedisSerializer
时,每个对象上会加上一个@class
属性,属性值就是对应的全类名,而后者没有这个@class
属性,所以在使用Jackson2JsonRedisSerializer
时如果强制类型转换会报错,所以推荐使用GenericJackson2JsonRedisSerializer
。
此外,需要用一个objectMapper来配置GenericJackson2JsonRedisSerializer
,否则在序列化JSONObject的时候会出错
1
| objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
setVisibility
方法可以设定访问类的字段和方法的权限
1
| objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
activateDefaultTyping
这个方法用于配置处理多态时能够正确的序列化和反序列化。
LaissezFaireSubTypeValidator
是一个比较宽松的类型验证器,instance是一个静态成员,实际上就是返回了一个LaissezFaireSubTypeValidator
的实例
1
| public static final LaissezFaireSubTypeValidator instance = new LaissezFaireSubTypeValidator();
|
DefaultTyping
指定如何处理类型信息
封装一个RedisUtils类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package org.zzb.redisusage.utils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component;
import java.util.List; import java.util.Set;
@Component public class RedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
@Autowired public RedisUtils(@Qualifier("myRedisTemplate") RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; }
public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value); }
public Object get(String key) { return redisTemplate.opsForValue().get(key); }
public Set<String> getAllKeys() { return redisTemplate.keys("*"); } }
|
包含了简单的set和get方法以及获取所有的key。
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @SpringBootTest @Slf4j class RedisUsageApplicationTests {
@Autowired RedisUtils redisUtils;
@Test void contextLoads() throws JSONException { String JavaPojo = "JavaPojo"; Person person01 = new Person("zzb", "English"); redisUtils.set(JavaPojo, person01);
String JSONObjectKey = "JSONObjectKey"; JSONObject jsonObject = new JSONObject(); jsonObject.put("name", "zzb"); jsonObject.put("age", 23); redisUtils.set(JSONObjectKey, jsonObject);
String JavaString = "JavaString"; redisUtils.set(JavaString, "v1");
String JavaList = "JavaList"; List<Person> list = new ArrayList<>(); list.add(person01); redisUtils.set(JavaList, list);
String JSONArray = "JSONArray"; JSONArray jsonArray = new JSONArray(); jsonArray.put(person01); redisUtils.set(JSONArray, jsonArray);
log.info("JavaPojo -> {}", redisUtils.get(JavaPojo)); log.info("JavaString -> {}", redisUtils.get(JavaString)); log.info("JSONObject -> {}", redisUtils.get(JSONObjectKey)); log.info("ArrayList -> {}", redisUtils.get(JavaList)); log.info("JSONArray -> {}", redisUtils.get(JSONArray));
log.info("All keys -> {}", redisUtils.getAllKeys()); } }
|
Output
1 2 3 4 5 6
| 2024-09-02T16:46:24.048+08:00 INFO 61606 --- [redis-usage] [ main] o.z.r.RedisUsageApplicationTests : JavaPojo -> Person(name=zzb, language=English) 2024-09-02T16:46:24.050+08:00 INFO 61606 --- [redis-usage] [ main] o.z.r.RedisUsageApplicationTests : JavaString -> v1 2024-09-02T16:46:24.056+08:00 INFO 61606 --- [redis-usage] [ main] o.z.r.RedisUsageApplicationTests : JSONObject -> {"name":"zzb","age":23} 2024-09-02T16:46:24.059+08:00 INFO 61606 --- [redis-usage] [ main] o.z.r.RedisUsageApplicationTests : ArrayList -> [Person(name=zzb, language=English)] 2024-09-02T16:46:24.061+08:00 INFO 61606 --- [redis-usage] [ main] o.z.r.RedisUsageApplicationTests : JSONArray -> ["Person(name=zzb, language=English)"] 2024-09-02T16:46:24.066+08:00 INFO 61606 --- [redis-usage] [ main] o.z.r.RedisUsageApplicationTests : All keys -> [JSONObjectKey, JavaString, JavaPojo, JSONArray, JavaList]
|
在redis-cli中尝试获取刚才的数据
美化一下JSON之后得到的输出(字符串v1就不展示了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| [ "org.zzb.redisusage.pojo.Person", { "name": "zzb", "language": "English" } ]
[ "org.json.JSONObject", { "nameValuePairs": [ "java.util.HashMap", { "name": "zzb", "age": 23 } ] } ]
[ "java.util.ArrayList", [ [ "org.zzb.redisusage.pojo.Person", { "name": "zzb", "language": "English" } ] ] ]
[ "org.json.JSONArray", { "values": [ "java.util.ArrayList", [ [ "org.zzb.redisusage.pojo.Person", { "name": "zzb", "language": "English" } ] ] ] } ]
|