文章目录
①. Spring Cache概述
- ①. 如何找到Spring Cache的官方文档
②.Spring 从 3.1开始定义了org.springframework.cache.Cache和 org.springframework.cache.Cache Manager接口来统一不同的缓存技术,并支持使用 JCache(JSR-107)注解简化我们开发
③. JSR-107定义了5个核心接口来实现缓存操作,分别是CachingProvider, CacheManager, Cache, Entry和Expiry
④. 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取
⑤. SpringCache常用注解详解
注解解释@Cacheable触发将数据保存到缓存的操作@CacheEvict触发将数据从缓存删除的操作@CachePut不影响方法执行更新缓存 双写模式@Caching组合以上多个操作@CacheConfig在类级别共享缓存的相同配置②. 触发缓存入口 - @Cacheable
①. @Cacheable 注解提供了一些参数,用于配置缓存的行为。下面是一些常用的参数:
@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceCacheable{@AliasFor("cacheNames")String[]value()default{};@AliasFor("value")String[]cacheNames()default{};Stringkey()default"";StringkeyGenerator()default"";StringcacheManager()default"";StringcacheResolver()default"";Stringcondition()default"";Stringunless()default"";booleansync()defaultfalse;}
- ②. value/cacheNames属性
- 这两个属性代表的意义相同,根据@AliasFor注解就能看出来了。
- 这两个属性都是用来指定缓存组件的名称,即将方法的返回结果放在哪个缓存中,属性定义为数组,可以指定多个缓存
@AliasFor("cacheNames")String[]value()default{};@AliasFor("value")String[]cacheNames()default{};
@Cacheable(cacheNames ={CacheEnum.RedisCacheNameExpGroup.AN_MINUTE,CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES})//这两种配置等价@Cacheable(value ="user")//@Cacheable(cacheNames = "user")UsergetUser(Integer id);
@Cacheable(cacheNames =CacheEnum.RedisCacheNameExpGroup.AN_MINUTE, cacheManager =CacheEnum.CacheManager.REDIS_MANAGER,
key =CacheEnum.RedisCacheKeys.PATROL_BOARD+"+#params.areaCode"+"+#params.staffCode")
- ③. key属性:可以通过key属性来指定缓存数据所使用的的key,默认使用的是方法调用传过来的参数作为key
@Cacheable(value ="user",key ="#root.method.name")UsergetUser(Integer id);
- ④. cacheManager属性:用来指定缓存管理器。针对不同的缓存技术,需要实现不同的 cacheManager,Spring 也为我们定义了如下的一些cacheManger实现
- ⑤. unless属性,意为"除非"的意思。即只有unless指定的条件为true时,方法的返回值才不会被缓存。可以在获取到结果后进行判断
@Cacheable(value ="user",unless ="#result==null || #result.size()==0")//当方法返回值为 null 时,就不缓存UsergetUser(Integer id);@Cacheable(value ="user",unless ="#a0 == 1")//如果第一个参数的值是1,结果不缓存UsergetUser(Integer id);
- ⑥. condition:条件判断属性,用来指定符合指定的条件下才可以缓存。也可以通过SpEL 表达式进行设置。这个配置规则和上面表格中的配置规则是相同的
@Cacheable(value ="user",condition ="#id>0")//传入的 id 参数值>0才进行缓存UsergetUser(Integer id);
- ⑦. sync该属性用来指定是否使用异步模式,该属性默认值为false,默认为同步模式。异步模式指定sync = true即可,异步模式下unless属性不可用
③. 更新缓存 - CachePut
- ①. @CachePut:不影响方法执行更新缓存(双写模式) 需要有返回值
- ②. @CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中
- ③. @Cacheput注解一般使用在保存,更新方法中
@CachePut(value="emp",key ="#result.id")publicEmployeeupdateEmployee(Employee employee){System.out.println("updateEmp:"+employee);
employeeMapper.updateEmployee(employee);return employee;}
④. 删除缓存 - CacheEvict
- @CacheEvict:触发将数据从缓存删除的操作
@CacheEvict(cacheNames =CacheEnum.RedisCacheNameExpGroup.ONE_DAY, cacheManager =CacheEnum.CacheManager.REDIS_MANAGER,
key =CacheEnum.RedisCacheKeys.DISTRICT_DETAIL+"+#params.areaCode"+"+#params.timeRange")publicvoiddistrictDetail(DiagnosisParams params){
log.info("refresh districtDetail!,areaCode:{},timeRange:{}",params.getAreaCode(),params.getTimeRange());}
⑤. 组合操作- Caching
- ①. 组合以上多个操作: @Caching不常用
- @Caching 注解可以在一个方法或者类上同时指定多个Spring Cache相关的注解。
- 其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和
- ②. @CacheEvict。对于一个数据变动,更新多个缓存的场景,可以通过@Caching来实现:
@Caching(cacheable =@Cacheable(cacheNames ="caching", key ="#age"), evict =@CacheEvict(cacheNames ="t4", key ="#age"))publicStringcaching(int age){return"caching: "+ age +"-->"+UUID.randomUUID().toString();}
⑥. 共享缓存配置 - CacheConfig
- ①. @CacheConfig不常用:@CacheConfig是Spring提供的一个注解,用于统一配置缓存的一些属性,例如缓存名称、缓存管理器等
- ②. 使用@CacheConfig注解需要注意以下几点:
- @CacheConfig可以放在类上,也可以放在方法上。如果放在类上,则该类中所有的缓存方法都会继承该注解的属性。
- @CacheConfig的属性可以被缓存方法上的@Cacheable、@CachePut、@CacheEvict等注解覆盖。
- @CacheConfig的属性可以通过Spring的EL表达式进行动态配置。
// 在上面的示例中,@CacheConfig注解指定了缓存名称为"myCache",缓存管理器为"myCacheManager"。这些属性会被该类中所有的缓存方法继承// 但是,如果某个缓存方法上使用了@Cacheable、@CachePut、@CacheEvict等注解,并且指定了相同的属性,则该注解的属性会覆盖@CacheConfig注解的属性@CacheConfig(cacheNames ="myCache", cacheManager ="myCacheManager")@ServicepublicclassMyService{@Cacheable(key ="#id")publicMyObjectfindById(Long id){// ...}@CachePut(key ="#myObject.id")publicMyObjectsave(MyObject myObject){// ...}@CacheEvict(key ="#id")publicvoiddeleteById(Long id){// ...}}
⑦. 从0搭建缓存项目
- ①. 导入依赖
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>study2022_xz</artifactId><groupId>com.xiaozhi</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>springboot-swagger</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><knife4j.version>2.0.9</knife4j.version><lombok.version>1.18.12</lombok.version><caffeine.version>2.7.0</caffeine.version></properties><dependencies><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>${knife4j.version}</version></dependency><!--web场景--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.80</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--引入caffeine依赖--><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>${caffeine.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!--引入guava依赖--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version></dependency><!--引入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><scope>compile</scope></dependency></dependencies></project>
- ②. 配置类 - 常量分组概念
publicinterfaceCacheEnum{/**
* cacheTemplate
*/interfaceCacheTemplates{/**
* 业务用redis
*/StringREDIS_CACHE_TEMPLATE="redisTemplate";}/**
* cacheManager
*/interfaceCacheManager{/**
* caffeine Cache Manager
*/StringCAFFEINE_MANAGER="caffeineManager";/**
* redis Cache Manager
*/StringREDIS_MANAGER="redisCacheManager";}/**
* redis keys
*/interfaceRedisCacheKeys{StringSSM_USER_KEY="SSM:USER_KEY:";}/**
* redis exp group
*/interfaceRedisCacheNameExpGroup{StringAN_MINUTE="ssm:anMinute";StringFIVE_MINUTES="ssm:fiveMinute";StringAN_HOUR="ssm:anHour";StringTWO_HOUR="ssm:twoHour";StringFIVE_HOURS="ssm:fiveHour";StringONE_DAY="ssm:oneDay";StringTWO_DAY="ssm:twoDay";}/**
* caffeine cache names conf
*/@AllArgsConstructor@GetterenumCaffeineCacheNamesConf{SSM_TEST(CaffeineCacheNames.SSM_TEST,100,10000,1,TimeUnit.DAYS),;privateString cacheName;privateint initialCapacity;privateint maximumSize;privateint expsNum;privateTimeUnit timeUnit;}/**
* Caffeine Cache Names
*/interfaceCaffeineCacheNames{StringSSM_TEST="SSM_TEST";}}
/**
* caffeine缓存配置,根据{@link CacheEnum.CaffeineCacheNamesConf}配置,
* 动态设置参数以及自动加载策略
*/@ConfigurationpublicclassCaffeineCacheConfig{@Bean(CacheEnum.CacheManager.CAFFEINE_MANAGER)publicCacheManagercacheManagerWithCaffeine(){SimpleCacheManager caffeineCacheManager =newSimpleCacheManager();
caffeineCacheManager.setCaches(buildCaffeineCache());return caffeineCacheManager;}//动态设置缓存参数privateList<CaffeineCache>buildCaffeineCache(){ArrayList<CaffeineCache> caches =Lists.newArrayList();CacheEnum.CaffeineCacheNamesConf[] cacheNames =CacheEnum.CaffeineCacheNamesConf.values();for(CacheEnum.CaffeineCacheNamesConf nameCof : cacheNames){
caches.add(newCaffeineCache(nameCof.getCacheName(),Caffeine.newBuilder().recordStats().initialCapacity(nameCof.getInitialCapacity()).maximumSize(nameCof.getMaximumSize()).expireAfterWrite(nameCof.getExpsNum(), nameCof.getTimeUnit()).build()));}return caches;}}
@EnableCaching@Configuration@Slf4jpublicclassRedisConfigextendsJCacheConfigurerSupport{@Value("${spring.redis.database}")privateint dbIndex;@Value("${spring.redis.host}")privateString host;@Value("${spring.redis.port}")privateint port;@Value("${spring.redis.password}")privateString password;@Value("${spring.redis.timeout}")privateint timeout;@Value("${spring.redis.lettuce.pool.max-active}")privateint redisPoolMaxActive;@Value("${spring.redis.lettuce.pool.max-wait}")privateint redisPoolMaxWait;@Value("${spring.redis.lettuce.pool.max-idle}")privateint redisPoolMaxIdle;@Value("${spring.redis.lettuce.pool.min-idle}")privateint redisPoolMinIdle;publicRedisSerializer<Object>redisSerializer(){ObjectMapper objectMapper =newObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS,true);
objectMapper.configure(SerializationFeature.INDENT_OUTPUT,true);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE,false);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);returnnewGenericJackson2JsonRedisSerializer(objectMapper);}publicLettuceConnectionFactorylettuceConnectionFactory(){RedisStandaloneConfiguration configuration =newRedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
configuration.setDatabase(dbIndex);if(!ObjectUtils.isEmpty(password)){RedisPassword redisPassword =RedisPassword.of(password);
configuration.setPassword(redisPassword);}GenericObjectPoolConfig genericObjectPoolConfig =newGenericObjectPoolConfig();
genericObjectPoolConfig.setMaxTotal(redisPoolMaxActive);
genericObjectPoolConfig.setMinIdle(redisPoolMinIdle);
genericObjectPoolConfig.setMaxIdle(redisPoolMaxIdle);
genericObjectPoolConfig.setMaxWaitMillis(redisPoolMaxWait);LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder =LettucePoolingClientConfiguration.builder();
builder.poolConfig(genericObjectPoolConfig);
builder.commandTimeout(Duration.ofSeconds(timeout));LettuceConnectionFactory connectionFactory =newLettuceConnectionFactory(configuration, builder.build());
connectionFactory.afterPropertiesSet();return connectionFactory;}@Bean(CacheEnum.CacheTemplates.REDIS_CACHE_TEMPLATE)publicRedisTemplate<String,Object>redisTemplate(){RedisTemplate<String,Object> redisTemplate =newRedisTemplate();
redisTemplate.setKeySerializer(newStringRedisSerializer());
redisTemplate.setHashKeySerializer(newStringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer());
redisTemplate.setValueSerializer(redisSerializer());
redisTemplate.setConnectionFactory(lettuceConnectionFactory());
redisTemplate.afterPropertiesSet();return redisTemplate;}@Bean(CacheEnum.CacheManager.REDIS_MANAGER)@PrimarypublicCacheManagerredisCacheManager(){RedisCacheConfiguration defConfig =RedisCacheConfiguration.defaultCacheConfig();ImmutableSet.Builder<String> cacheNames =ImmutableSet.builder();ImmutableMap.Builder<String,RedisCacheConfiguration> cacheConfig =ImmutableMap.builder();HashMap<String,Duration> exps =newHashMap<>();
exps.put(CacheEnum.RedisCacheNameExpGroup.AN_MINUTE,Duration.ofMinutes(1));
exps.put(CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES,Duration.ofMinutes(5));
exps.put(CacheEnum.RedisCacheNameExpGroup.AN_HOUR,Duration.ofHours(1));
exps.put(CacheEnum.RedisCacheNameExpGroup.TWO_HOUR,Duration.ofHours(2));
exps.put(CacheEnum.RedisCacheNameExpGroup.FIVE_HOURS,Duration.ofHours(5));
exps.put(CacheEnum.RedisCacheNameExpGroup.ONE_DAY,Duration.ofDays(1));
exps.put(CacheEnum.RedisCacheNameExpGroup.TWO_DAY,Duration.ofDays(2));for(String cacheName : exps.keySet()){
defConfig = defConfig.entryTtl(exps.get(cacheName));
cacheConfig.put(cacheName, defConfig);
cacheNames.add(cacheName);}returnRedisCacheManager.builder(lettuceConnectionFactory()).initialCacheNames(cacheNames.build()).withInitialCacheConfigurations(cacheConfig.build()).build();}@OverridepublicCacheErrorHandlererrorHandler(){returnnewCacheErrorHandler(){@OverridepublicvoidhandleCacheGetError(RuntimeException exception,Cache cache,Object key){
log.error("handleCacheGetError!!!->{}", exception.getMessage());}@OverridepublicvoidhandleCachePutError(RuntimeException exception,Cache cache,Object key,Object value){
log.error("handleCachePutError!!!->{}", exception.getMessage());}@OverridepublicvoidhandleCacheEvictError(RuntimeException exception,Cache cache,Object key){
log.error("handleCacheEvictError!!!->{}", exception.getMessage());}@OverridepublicvoidhandleCacheClearError(RuntimeException exception,Cache cache){
log.error("handleCacheClearError!!!->{}", exception.getMessage());}};}}
- ③. 测试
@GetMapping("/testUser")@Cacheable(cacheNames ={CacheEnum.RedisCacheNameExpGroup.AN_MINUTE,CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES},// 缓存组概念
cacheManager =CacheEnum.CacheManager.REDIS_MANAGER,// 使用哪个manager管理
condition ="#a0>2",// 满足条件
key ="#root.method.name",// key = 当前方法名词
unless ="#result==null || #result.size()==0")// 值为空无效//key = "'" + CacheEnum.RedisCacheKeys.SSM_USER_KEY + "'" + "+#userId", unless = "#result==null || #result.size()==0")publicList<Integer>testUser(int userId){returnLists.newArrayList(userId);}
版权归原作者 所得皆惊喜 所有, 如有侵权,请联系我们删除。