serializer = new Jackson2JsonRedisSerializer<>(Object.class ) ; ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // https://www.cnblogs.com/shanheyongmu/p/15157378.html // objectMapper.enableDefaultTyping()被弃用 objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); serializer.setObjectMapper(objectMapper); return serializer; } }通过 RedisCacheConfiguration 设置超时时间,来避免产生很多不必要的缓存数据。
@Bean public RedisCacheManager redisCacheManager (RedisConnectionFactory redisConnectionFactory) { RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); //设置Redis缓存有效期为1天 RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1 )); return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration); }
第四步 ,在标签更新接口中添加 @CachePut 注解,也就是说方法执行前不会去缓存中找,但方法执行完会将返回值放入缓存中。
@Controller @Api (tags = "标签" )@RequestMapping ("/postTag" )public class PostTagController { @Autowired private IPostTagService postTagService; @Autowired private IPostTagRelationService postTagRelationService; @RequestMapping (value = "/update" , method = RequestMethod.POST) @ResponseBody @ApiOperation ("修改标签" ) @CachePut (value = "codingmore" , key = "'codingmore:postags:'+#postAddTagParam.postTagId" ) public ResultObject update (@Valid PostTagParam postAddTagParam) { if (postAddTagParam.getPostTagId() == null ) { return ResultObject.failed("标签id不能为空" ); } PostTag postTag = postTagService.getById(postAddTagParam.getPostTagId()); if (postTag == null ) { return ResultObject.failed("标签不存在" ); } QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("description" , postAddTagParam.getDescription()); int count = postTagService.count(queryWrapper); if (count > 0 ) { return ResultObject.failed("标签名称已存在" ); } BeanUtils.copyProperties(postAddTagParam, postTag); return ResultObject.success(postTagService.updateById(postTag) ? "修改成功" : "修改失败" ); } }
注意看 @CachePut 注解这行代码:
@CachePut (value = "codingmore" , key = "'codingmore:postags:'+#postAddTagParam.postTagId" )
value:缓存名称,也就是缓存的命名空间,value 这里应该换成 namespace 更好一点; key:用于在命名空间中缓存的 key 值,可以使用 SpEL 表达式,比如说 'codingmore:postags:'+#postAddTagParam.postTagId
; 还有两个属性 unless 和 condition 暂时没用到,分别表示条件符合则不缓存,条件符合则缓存。 第五步 ,启动服务器端,启动客户端,修改标签进行测试。
通过 Red 客户端(一款 macOS 版的 Redis 桌面工具),可以看到刚刚更新的返回值已经添加到 Redis 中了。
三、使用 Redis 连接池Redis 是基于内存的数据库,本来是为了提高程序性能的,但如果不使用 Redis 连接池的话,建立连接、断开连接就需要消耗大量的时间。
用了连接池,就可以实现在客户端建立多个连接,需要的时候从连接池拿,用完了再放回去,这样就节省了连接建立、断开的时间。
要使用连接池,我们得先了解 Redis 的客户端,常用的有两种:Jedis 和 Lettuce。
Jedis:Spring Boot 1.5.x 版本时默认的 Redis 客户端,实现上是直接连接 Redis Server,如果在多线程环境下是非线程安全的,这时候要使用连接池为每个 jedis 实例增加物理连接; Lettuce:Spring Boot 2.x 版本后默认的 Redis 客户端,基于 Netty 实现,连接实例可以在多个线程间并发访问,一个连接实例不够的情况下也可以按需要增加连接实例。 它俩在 GitHub 上都挺受欢迎的,大家可以按需选用。
我这里把两种客户端的情况都演示一下,方便小伙伴们参考。
1)Lettuce
第一步 ,修改 application-dev.yml,添加 Lettuce 连接池配置(pool 节点)。
spring: redis: lettuce: pool: max-active: 8 # 连接池最大连接数 max-idle: 8 # 连接池最大空闲连接数 min-idle: 0 # 连接池最小空闲连接数 max-wait: -1ms # 连接池最大阻塞等待时间,负值表示没有限制
第二步 ,在 pom.xml 文件中添加 commons-pool2 依赖,否则会在启动的时候报 ClassNotFoundException 的错。这是因为 Spring Boot 2.x 里默认没启用连接池。
Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader .loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 153 common frames omitted
添加 commons-pool2 依赖:
org.apache.commons commons-pool2 2.6.2 <type >jartype> compile
重新启动服务,在 RedisConfig 类的 redisTemplate 方法里对 redisTemplate 打上断点,debug 模式下可以看到连接池的配置信息(redisConnectionFactory→clientConfiguration→poolConfig
)。如下图所示。
如果在 application-dev.yml 文件中没有添加 Lettuce 连接池配置的话,是不会看到
2)Jedis
第一步 ,在 pom.xml 文件中添加 Jedis 依赖,去除 Lettuce 默认依赖。
org.springframework.boot spring-boot-starter-data-redis io.lettuce lettuce-core redis.clients jedis
第二步 ,修改 application-dev.yml,添加 Jedis 连接池配置。
spring: redis: jedis: pool: max-active: 8 # 连接池最大连接数 max-idle: 8 # 连接池最大空闲连接数 min-idle: 0 # 连接池最小空闲连接数 max-wait: -1ms # 连接池最大阻塞等待时间,负值表示没有限制
启动服务后,观察 redisTemplate 的 clientConfiguration 节点,可以看到它的值已经变成 DefaultJedisClientConfiguration 对象了。
当然了,也可以不配置 Jedis 客户端的连接池,走默认的连接池配置。因为 Jedis 客户端默认增加了连接池的依赖包,在 pom.xml 文件中点开 Jedis 客户端依赖可以查看到。
四、自由操作 RedisSpring Cache 虽然提供了操作 Redis 的便捷方法,比如我们前面演示的 @CachePut 注解,但注解提供的操作非常有限,比如说它只能保存返回值到缓存中,而返回值并不一定是我们想要保存的结果。
与其保存这个返回给客户端的 JSON 信息,我们更想保存的是更新后的标签。那该怎么自由地操作 Redis 呢?
第一步 ,增加 RedisService 接口:
public interface RedisService { /** * 保存属性 */ void set (String key, Object value) ; /** * 获取属性 */ Object get (String key) ; /** * 删除属性 */ Boolean del (String key) ; ... // 更多方法见:https://github.com/itwanger/coding-more/blob/main/codingmore-mbg/src/main/java/com/codingmore/service/RedisService.java }
第二步 ,增加 RedisServiceImpl 实现类:
@Service public class RedisServiceImpl implements RedisService { @Autowired private RedisTemplate redisTemplate; @Override public void set (String key, Object value) { redisTemplate.opsForValue().set(key, value); } @Override public Object get (String key) { return redisTemplate.opsForValue().get(key); } @Override public Boolean del (String key) { return redisTemplate.delete(key); } // 更多代码参考:https://github.com/itwanger/coding-more/blob/main/codingmore-mbg/src/main/java/com/codingmore/service/impl/RedisServiceImpl.java }
第三步 ,在标签 PostTagController 中增加 Redis 测试用接口 simpleTest :
@Controller @Api (tags = "标签" )@RequestMapping ("/postTag" )public class PostTagController { @Autowired private IPostTagService postTagService; @Autowired private IPostTagRelationService postTagRelationService; @Autowired private RedisService redisService; @RequestMapping (value = "/simpleTest" , method = RequestMethod.POST) @ResponseBody @ApiOperation ("修改标签/Redis 测试用" ) public ResultObject simpleTest (@Valid PostTagParam postAddTagParam) { if (postAddTagParam.getPostTagId() == null ) { return ResultObject.failed("标签id不能为空" ); } PostTag postTag = postTagService.getById(postAddTagParam.getPostTagId()); if (postTag == null ) { return ResultObject.failed("标签不存在" ); } QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("description" , postAddTagParam.getDescription()); int count = postTagService.count(queryWrapper); if (count > 0 ) { return ResultObject.failed("标签名称已存在" ); } BeanUtils.copyProperties(postAddTagParam, postTag); boolean successFlag = postTagService.updateById(postTag); String key = "redis:simple:" + postTag.getPostTagId(); redisService.set(key, postTag); PostTag cachePostTag = (PostTag) redisService.get(key); return ResultObject.success(cachePostTag); } }
第四步 ,重启服务,使用 Knife4j 测试该接口 :
然后通过 Red 查看该缓存,OK,确认我们的代码是可以完美执行的。
五、小结赞美 Redis 的彩虹屁我就不再吹了,总之,如果我是 Redis 的作者 Antirez,我就自封为神!
编程喵实战项目的源码地址我贴下面了,大家可以下载下来搞一波了:
https://github.com/itwanger/coding-more
我们下期见~
没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟 。
推荐阅读 :
浏览
31 点赞
评论
收藏
分享
手机扫一扫分享
分享
举报
点赞
评论
收藏
分享
手机扫一扫分享
分享
举报