前言 说起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 -> v12024 -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" } ] ] ] } ]