前言
前文简单介绍了一下作者将会开源一个项目——ATP应用测试平台。源码地址:atp: 应用测试平台。后续作者将会将项目中常用到的技术栈案例整理成集,发布到该开源项目中,并配套详细的博客讲解,方便各位读者的使用。本篇我们主要讲解一下在springboot中关于缓存的应用案例。使用的主要技术是非关系型数据库redis,以及spring技术栈的中SpringCache,作者这里不会大篇幅的涉及到原理性的东西,重点在于实战的部分。缓存是我们应用开发中必不可少的部分,使用缓存能够大幅提升系统的性能,尤其对于我们系统中的热点数据,修改频次低,并发访问量巨大。对于高并发的响应,如果系统没有缓存,我们的系统将会出现灾难性的后果,甚至上线及宕机。这里我们使用SpringCache技术栈实现应用的缓存,只需简单的配置就能够使用缓存。
正文
- pom配置
在pom文件中引入redis启动器及连接池工具包
<!--redis启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--连接池工具--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
- application.yml配置redis连接
说明:redis的搭建过程,读者可以参考我的文章docker环境下docker-compose安装高可用redis集群详解(一主二从三哨兵),搭建一个高可用的redis集群,我们这里直接在yml中配置我们的redis集群。
spring: redis: #默认数据分区 database: 0 #redis集群节点配置 cluster: nodes: - 192.168.23.134:6379 - 192.168.23.134:6380 - 192.168.23.134.6381 max-redirects: 3 #超时时间 timeout: 10000 #哨兵节点配置 sentinel: master: mymaster nodes: - 192.168.23.134:26379 - 192.168.23.134:26380 - 192.168.23.134:26381 #redis密码 password: root #redis 客户端工具 lettuce: pool: # 连接池最大连接数(使用负值表示没有限制) 默认为8 max-active: 8 # 连接池中的最小空闲连接 默认为 0 min-idle: 1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1 max-wait: 1000 # 连接池中的最大空闲连接 默认为8 max-idle: 8
- 创建缓存配置类RedisConfig,并通过注解方式开启缓存
package com.yundi.atp.platform.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import org.springframework.cache.CacheManager; 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.StringRedisSerializer; import java.time.Duration; /** * @Author: yanp * @Description: redis配置 * @Date: 2021/4/25 15:00 * @Version: 1.0.0 */ @EnableCaching @Configuration public class RedisConfig { /** * 缓存工具类创建 * @param factory * @return */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { // 缓存序列化配置,避免存储乱码 RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } /** * 缓存管理配置 * @param redisTemplate * @return */ @Bean public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { // 基本配置 RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration .defaultCacheConfig() // 设置key为String .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer())) // 设置value为自动转Json的Object .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())) // 不缓存null .disableCachingNullValues() // 缓存数据保存1小时 .entryTtl(Duration.ofHours(1)); //创建一个redis缓存管理器 RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder // Redis连接工厂 .fromConnectionFactory(redisTemplate.getConnectionFactory()) // 缓存配置 .cacheDefaults(defaultCacheConfiguration) // 配置同步修改或删除put/evict .transactionAware() .build(); return redisCacheManager; } }
- 缓存使用
- 注解说明:
缓存的使用十分简单,我们只需要将需要缓存的方法加上注解就可以实现一些数据方法的缓存。
@Cacheable:可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。该注解标记的方法,Spring通过key-value的方式会在其被调用后将其返回值缓存起来,以保证下次利用同样请求的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。
@Cacheable(cacheNames = "user", key = "#id") @Override public User findUserInfoById(String id) { return this.getById(id); }
@CachePut:与@Cacheable不同的是,使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CacheEvict:标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict(cacheNames = "user", key = "#id") @Override public void removeUserById(String id) { this.removeById(id); }
@Caching:注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
- 注解参数说明:
value:指定cache名称
key:指定存储数据的键,该属性支持SpringEL表达式,可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”或者使用root对象可以用来生成key。
root方式生成key 属性名称 说明 示例 methodName 当前方法名 #root.methodName method 当前方法 #root.method.name target 当前被调用的对象 #root.target targetClass 当前被调用的对象的class #root.targetClass args 当前方法参数组成的数组 #root.args[0] caches 当前被调用的方法使用的Cache #root.caches[0].name condition:缓存指定条件下的数据,支持SpringEL表达式表达式
@Cacheable(cacheNames = "user", key = "#id", condition = "#id%2==0") @Override public User findUserInfoById(String id) { return this.getById(id); }
allEntries:boolean类型,表示是否需要清除缓存中的所有元素。如果指定为true时,将会清除所有相同cacheNames下的数据,指定为false默认是根据key值清除。
beforeInvocation:boolean类型,如果为true,代表在方法执行前清除缓存,false,代表在方式执行后清除缓存,有可能会清除缓存失败。
- 存储验证
结语
ok,到这里我们的springboot+springcache+redis整个缓存服务的整合以及使用过程就结束了,具体的功能案例使用,请参考我的ATP应用测试平台中的相关代码,我们下期见。。。