💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 简介 **SpringBoot导入spring-boot-starter-data-redis时, CacheManager默认使用RedisCache.** 在 Spring Boot 中,默认集成的 Redis 就是 Spring Data Redis,默认底层的连接池使用了 lettuce ,开发者可以自行修改为自己的熟悉的,例如 Jedis。 Spring Data Redis 针对 Redis 提供了非常方便的操作模板 RedisTemplate 。这是 Spring Data 擅长的事情,那么接下来我们就来看看 Spring Boot 中 Spring Data Redis 的具体用法。 Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,它依赖于 spring-data-redis 和 lettuce。Spring Boot 1.0 默认使⽤的是 Jedis 客户端,2.0 替换成了 Lettuce 添加redis依赖 ~~~ <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.2.1.RELEASE</version> </dependency> <!-- 引⼊ commons-pool 2 是因为 Lettuce 需要使⽤ commons-pool 2 创建 Redis 连接池 --> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.8.0</version> </dependency> <!-- 在JDK1.8中的时间类,采用了一套了新的API。但是在反序列化中,会出现异常. 此依赖解决 --> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> ~~~ **如果将lettuce客户端换成jedis客户端** ~~~xml <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--使用redis连接池--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> ~~~ * Lettuce:是⼀个可伸缩线程安全的 Redis 客户端,多个线程可以共享同⼀个 RedisConnection,它利⽤ 优秀 Netty NIO 框架来⾼效地管理多个连接。 * Spring Data:是 Spring 框架中的⼀个主要项⽬,⽬的是为了简化构建基于 Spring 框架应⽤的数据访问,包括⾮关系数据库、Map-Reduce 框架、云数据服务等,另外也包含对关系数据库的访问⽀持。 * Spring Data Redis:是 Spring Data 项⽬中的⼀个主要模块,实现了对 Redis 客户端 API 的⾼度封装, 使对 Redis 的操作更加便捷。 # 配置连接信息 ~~~ ###################Redis################# # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password=root # 连接池最大连接数(使用负值表示没有限制) spring.redis.lettuce.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.lettuce.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.lettuce.pool.max-idle=10 # 连接池中的最小空闲连接 spring.redis.lettuce.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.lettuce.timeout=1000 ~~~ # redis存储中文显示问题 Redis的value存储中文后,get之后显示16进制的字符串”\\xe4\\xb8\\xad\\xe5\\x9b\\xbd”,如何解决? ~~~ 127.0.0.1:6379> set China 中国 OK 127.0.0.1:6379> get China "\xe4\xb8\xad\xe5\x9b\xbd" 127.0.0.1:6379> exit ~~~ 解决方法: 启动redis-cli时,在其后面加上**–raw**即可,汉字即可显示正常。 **–raw 使用RAW格式回帖(默认时是不是一个TTY标准)** # 配置 ## 序列化 针对StringRedisSerializer,Jackson2JsonRedisSerializer和JdkSerializationRedisSerializer进行测试 | 数据结构 | 序列化类 | 序列化前 | 序列化后 | | --- | --- | --- | --- | | Key/Value | StringRedisSerializer | test\_value | test\_value | | Key/Value | Jackson2JsonRedisSerializer | test\_value | "test\_value" | | Key/Value | JdkSerializationRedisSerializer | test\_value | 乱码 | | Hash | StringRedisSerializer | 2016-08-18 | 2016-08-18 | | Hash | Jackson2JsonRedisSerializer | 2016-08-18 | "2016-08-18" | | Hash | JdkSerializationRedisSerializer | 2016-08-18 | \\xAC\\xED\\x00\\x05t | **由此可以得到结论:** 1. StringRedisSerializer进行序列化后的值,在Java和Redis中保存的内容时一致的。 2. 用Jackson2JsonRedisSerializer序列化后,在Redis中保存的内容,比Java中多一对逗号。 3. 用JdkSerializationRedisSerializer序列化后,对于Key-Value结构来说,在Redis中不可读;对于Hash的Value来说,比Java的内容多了一些字符。 作者:小胖学编程 链接:https://www.jianshu.com/p/0d4aea41a70c 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ~~~ @Resource private RedisTemplate redisTemplate; @Resource private StringRedisTemplate stringRedisTemplate; ~~~ String Data Redis 为我们提供了 RedisTemplate 和 StringRedisTemplate 两个模版来进行数据操作,其中:StringRedisTemplate 只针对键值都是字符串的数据进行操作。 当我们的数据存储到 Redis 的时候,我们的键(key)和 值(value)都是通过 Spring 提供的 Serializer 序列化到数据可中的。 * RedisTemplate 默认使用的是 JdkSerializationRedisSerializer * StringRedisTemplate 默认使用的是 StringRedisSerializer Spring Data JPA 为我们提供了下面的 Serializer * GenericToStringSerializer * Jackson2JsonRedisSerializer * JdkSerializationRedisSerializer * OxmSerializer * StringRedisSerializer Spring Boot 为我们自动配置了 RedisTemplate,而 RedisTemplate 使用的是 JdkSerializationRedisSerializer,这个对我们用 redis 图形化客户端很不直观,因为 JdkSerializationRedisSerializer 使用二进制形式储存数据,在此我们将自己配置 RedisTemplate 并定义 Serializer ## 配置类 * **@EnableCaching**: 开启缓存 * **CacheManager**: Spring缓存管理器 * **KeyGenerator**: Redis 缓存键生成策略, Spring 默认的DefaultKeyGenerator根据参数列表生成Key,当参数列表的值相同时是一样的 就会造成获取到错误的缓存数据 ~~~ @EnableCaching注解是spring framework中的注解驱动的缓存管理功能, 当你在配置类(@Configuration)上使用@EnableCaching注解时, 会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。 如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。 ~~~ ~~~ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 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.cache.interceptor.KeyGenerator; 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.lang.reflect.Method; import java.time.Duration; import java.util.StringJoiner; @Configuration @EnableCaching //开启缓存 public class RedisConfig extends CachingConfigurerSupport{ /** * @description 缓存key前缀 */ private static final String keyPrefix = "CACHE:"; /** * 在这⾥可以为 Redis 设置⼀些全局配置,⽐如配置主键的⽣产策略 KeyGenerator,如不配置会默认使⽤参数名作为主键。在没有指定缓存 key 的情况下,key 的默认生成策略 * 注意: 该方法只是声明了key的生成策略,还未被使用,需在@Cacheable注解中指定keyGenerator * 如: @Cacheable(value = "key", keyGenerator = "keyGenerator") */ @Override @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { //接口提供三个参数,目标类,目标方法,目标参数列表 @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(RedisConfig.keyPrefix); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } @Bean(name="redisTemplate") public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, String> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); // 创建序列化类 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); //注意: 在JDK1.8中的时间类,采用了一套了新的API。但是在反序列化中,会出现异常. ckson-datatype-jsr310依赖解决 om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); om.registerModule(new JavaTimeModule()); //设置可见度 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; } //自定义 cacheManager 缓存管理器,全局 @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // 配置序列化,redis缓存管理器配置,默认使用 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() //.prefixKeysWith("prefix:") //设置静态前缀 ; RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith( // key 序列化方式 RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) // value 序列化方式 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) // 不缓存null值 //.disableCachingNullValues() //默认缓存过期时间 //.entryTtl(Duration.ofHours(3L)) ; RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(redisCacheConfiguration) .build(); return cacheManager; } } ~~~ ## 序列化问题 在JDK1.8中的时间类,采用了一套了新的API。但是在反序列化中,会出现异常。 ~~~ com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.time.LocalDate (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) ~~~ 在SpringBoot中的解决方案: * **在MAVEN中加入`ckson-datatype-jsr310`依赖。** ~~~xml <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> ~~~ * **配置Configuration中的ObjectMapper。** ~~~java @Bean public ObjectMapper serializingObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.registerModule(new JavaTimeModule()); return objectMapper; } ~~~ # 测试使用redis * opsForValue:对应 String(字符串) * opsForZSet:对应 ZSet(有序集合) * opsForHash:对应 Hash(哈希) * opsForList:对应 List(列表) * opsForSet:对应 Set(集合) * opsForGeo:对应 GEO(地理位置) ## string ~~~ @Autowired private RedisTemplate redisTemplate; @Test public void test() { redisTemplate.opsForValue().set("neo121", "ityouknow"); Assert.assertEquals("ityouknow", redisTemplate.opsForValue().get("neo121")); } ~~~ ## 对象存储 对象要有无参构造函数 ~~~ @Autowired private RedisTemplate redisTemplate; @Test public void test() { //对象要有无参构造函数 User user = new User(1, "jdxia"); ValueOperations<String, Object> operations = redisTemplate.opsForValue(); operations.set("com.jdxia", user); User u = (User) operations.get("com.jdxia"); System.out.println(u); } ~~~ ## 设置过期和查询是否存在 ~~~ //设置过期时间 redisTemplate.expire(key, time, TimeUnit.SECONDS); ~~~ ~~~ // @return 时间(秒) 返回0代表为永久有效;如果该key已经过期,将返回"-2"; redisTemplate.getExpire(key, TimeUnit.SECONDS); ~~~ ~~~ //string设置方式 //这个对象要有无参构造函数 User user = new User(2, "jdxia"); ValueOperations<String, Object> operations = redisTemplate.opsForValue(); //100毫秒 operations.set("com.jdxia", user, 100, TimeUnit.MILLISECONDS); User u = (User) operations.get("com.jdxia"); System.out.println(u); Thread.sleep(1000); Boolean hasKey = redisTemplate.hasKey("com.jdxia"); if (hasKey) { System.out.println("key is exists"); } else { System.out.println("key is not exists"); } ~~~ ## 递增减 ~~~ redisTemplate.opsForValue().increment(key, delta); redisTemplate.opsForValue().increment(key, -delta); ~~~ ## 删除数据 ~~~ redisTemplate.delete("deletekey"); ~~~ ## hash ~~~ HashOperations operations = redisTemplate.opsForHash(); operations.put("hash", "hashKey", "hashValue"); String v = (String) operations.get("hash", "hashKey"); System.out.println(v); ~~~ ~~~ //获取hashKey对应的所有键值 redisTemplate.opsForHash().entries(key); ~~~ ~~~ //map 对应多个键值, Map<String, Object> map redisTemplate.opsForHash().putAll(key, map); ~~~ ~~~ //可以用这个给hash设置过期时间 redisTemplate.expire(key, time, TimeUnit.SECONDS); ~~~ ~~~ //hash递增减 redisTemplate.opsForHash().increment(键, 项, 增减几); ~~~ ## list ~~~ ListOperations<String, String> list = redisTemplate.opsForList(); list.leftPush("list","it"); list.leftPush("list","you"); list.leftPush("list","know"); String value=(String)list.leftPop("list"); System.out.println("list value :"+value.toString()); ~~~ ~~~ //将list放入缓存, List<Object> value redisTemplate.opsForList().rightPushAll(key, value); ~~~ ~~~ //key: 键, start: 开始, end结束 -1代表所有值 redisTemplate.opsForList().range(key, start, end); ~~~ ~~~ //获取长度 redisTemplate.opsForList().size(key); ~~~ ~~~ //通过索引 获取list中的值, index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 redisTemplate.opsForList().index(key, index); ~~~ ~~~ //根据索引修改list中的某条数据 redisTemplate.opsForList().set(key, index, value); ~~~ ~~~ //移除n个值为value redisTemplate.opsForList().remove(key, n, value); ~~~ ## set Set 是可以⾃动排重的 ~~~ String key="set"; //添加 SetOperations<String, String> set = redisTemplate.opsForSet(); set.add(key,"it"); set.add(key,"you"); set.add(key,"you"); set.add(key,"know"); //获取 Set<String> values = set.members(key); for (String v:values){ System.out.println("set value :"+v); } ~~~ ~~~ //查询是否存在 redisTemplate.opsForSet().isMember(key, value); ~~~ ~~~ //获取长度 redisTemplate.opsForSet().size(key); ~~~ ~~~ //删除,values可以是多个 redisTemplate.opsForSet().remove(key, values); ~~~ Redis 为集合提供了求交集、并集、差集等操作,可以⾮常⽅便的使⽤ **差集** ~~~ SetOperations<String, String> set = redisTemplate.opsForSet(); String key1="setMore1"; String key2="setMore2"; set.add(key1,"it"); set.add(key1,"you"); set.add(key1,"you"); set.add(key1,"know"); set.add(key2,"xx"); set.add(key2,"know"); Set<String> diffs=set.difference(key1,key2); for (String v:diffs){ System.out.println("diffs set value :"+v); } ~~~ difference() 函数会把 key 1 中不同于 key 2 的数据对⽐出来,这个特性适合我 们在⾦融场景中对账的时候使⽤。 **交集** ~~~ SetOperations<String, String> set = redisTemplate.opsForSet(); String key3="setMore3"; String key4="setMore4"; set.add(key3,"it"); set.add(key3,"you"); set.add(key3,"xx"); set.add(key4,"aa"); set.add(key4,"bb"); set.add(key4,"know"); Set<String> unions=set.union(key3,key4); for (String v:unions){ System.out.println("unions value :"+v); } ~~~ unions 会取两个集合的合集,Set 还有其他很多类似的操作,⾮常⽅便我们对集合进⾏ 数据处理 Set 的内部实现是⼀个 Value 永远为 null 的 HashMap,实际就是通过计算 Hash 的⽅式来快速排重, 这也是 Set 能提供判断⼀个成员是否在集合内的原因。 ## zset Redis Sorted Set 的使⽤场景与 Set 类似,区别是 Set 不是⾃动有序的,⽽ Sorted Set 可以通过⽤户额外提供⼀个优先级(Score)的参数来为成员排序,并且是插⼊有序,即⾃动排序 ~~~ String key="zset"; redisTemplate.delete(key); ZSetOperations<String, String> zset = redisTemplate.opsForZSet(); zset.add(key,"it",1); zset.add(key,"you",6); zset.add(key,"know",4); zset.add(key,"neo",3); Set<String> zsets=zset.range(key,0,3); for (String v:zsets){ System.out.println("zset value : " + v ); } Set<String> zsetB=zset.rangeByScore(key,0,3); for (String v:zsetB){ System.out.println("zsetB value :"+v); } ~~~ 通过上⾯的例⼦我们发现插⼊到 Zset 的数据会⾃动根据 Score 进⾏排序,根据这个特性我们可以做优先队 列等各种常⻅见的场景。另外 Redis 还提供了 rangeByScore 这样的⼀个⽅法,可以只获取 Score 范围内排序 后的数据。 Redis Sorted Set 的内部使⽤ HashMap 和跳跃表(SkipList)来保证数据的存储和有序,HashMap ⾥放的是成员到 Score 的映射,⽽跳跃表⾥存放的是所有的成员,排序依据是 HashMap ⾥存的 Score, 使⽤跳跃表的结构可以获得⽐较⾼的查找效率,并且在实现上⽐较简单。 ## 查找 ~~~ Set<Serializable> keys = redisTemplate.keys(pattern); ~~~ # 整合session Spring 为 Spring Session 和 Redis 的集成提供了组件:`spring-session-data-redis` ~~~ <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> ~~~ **配置** ~~~ import org.springframework.context.annotation.Configuration; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; @Configuration @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30) public class SessionConfig {} ~~~ **测试** ~~~ @RequestMapping(value = "/setSession") public Map<String, Object> setSession (HttpServletRequest request){ Map<String, Object> map = new HashMap<>(); request.getSession().setAttribute("message", request.getRequestURL()); map.put("request Url", request.getRequestURL()); return map; } @RequestMapping(value = "/getSession") public Object getSession (HttpServletRequest request){ Map<String, Object> map = new HashMap<>(); map.put("sessionId", request.getSession().getId()); map.put("message", request.getSession().getAttribute("message")); return map; } ~~~