一.概念
1.主要注解:
注解总览:
- @EnableCaching: 开启spring cache功能
- @Cacheable:添加缓存
- @CacheEvict:删除缓存
- @CachePut:更新缓存,方法依旧执行,通常用于更新方法
- @Caching:在一个方法中同时使用多个缓存规则
- @CacheConfig:在类级别上设置一些通用的属性
2.@Cacheable/@CachePut/@CacheEvict 主要的参数:
value: 缓存的key值,必须指定至少一个,可指定多个 ,指定多个将依次查询缓存直到命中 。
例如:@Cacheable(value=”cache”) 或者@Cacheable(value={”cache1”,”cache2”})
key: 缓存的key值,可为空,若为空按照KeyGenerator的生成规则来。支持 SpEL 表达式。
例如:@Cacheable(value=”cache”,key=”#id”)
condition:指定缓存条件,可以为空,支持 SpEL ,返回 true 或者 false,只有为 true 才进行缓存/清除缓存。
例如:@Cacheable(value=”cache”,condition=”#name.length()>2”)
unless:否定缓存。当条件结果为TRUE时,就不会缓存。
例如:@Cacheable(value=”cache”,unless=”#name.length()>2”)
allEntries:(@CacheEvict ) 是否清空value定义的所有缓存,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
例如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation:(@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
例如:@CachEvict(value=”cache”,beforeInvocation=true)
3.SpEL上下文数据:
注意:
- 当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如@Cacheable(key = "targetClass + methodName +#p0")
- 使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:@Cacheable(value="cache", key="#id") @Cacheable(value="cache", key="#p0")
4.SpEL运算符:
二.使用
背景简述:
自Spring3.1开始,Spring就自带了对缓存的支持。我们可以直接使用Spring缓存技术将某些数据放入本机的缓存中;Spring缓存技术也可以搭配其他缓存中间件(如Redis等)进行使用,将某些数据写入到缓存中间件(缓存中间件可能在其他机器上)中。
1.引入缓存
-
spring-boot中引入缓存
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
2.使用@EnableCaching启动缓存
-
启用缓存
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
# 使用Redis做缓存
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory).build();
}
}
3.使用
@EnableCaching:开关性注解,在项目启动类或某个配置类上使用此注解后,则表示允许使用注解的方式进行缓存操作,如:
@Cacheable:可用于类或方法上;在目标方法执行前,会根据key先去缓存中查询看是否有数据,有就直接返回缓存中的key对应的value值。不再执行目标方法;无则执行目标方法,并将方法的返回值作为value,并以键值对的形式存入缓存,如:
@CachePut:可用于类或方法上;在执行完目标方法后,并将方法的返回值作为value,并以键值对的形式存入缓存中,如:
@CacheEvict:可用于类或方法上;在执行完目标方法后,清除缓存中对应key的数据(如果缓存中有对应key的数据缓存的话),如:
@Caching:此注解即可作为@Cacheable、@CacheEvict、@CachePut三种注解中的的任何一种或几种来使用,如:
@CacheConfig:@Cacheable、@CacheEvict、@CachePut这三个注解的cacheNames属性是必填项(或value属性是必填项,因为value属性是cacheNames的别名属性);如果上述三种注解都用的是同一个cacheNames的话,那么在每此都写cacheNames的话,就会显得麻烦。如将@CacheConfig注解就是来配置一些公共属性(如:cacheNames、keyGenerator等)的值的,如:
4.缓存注解的常用属性(以示例进行说明):
key:
key的来源可分为三类,分别是:默认的、keyGenerator生成的、主动指定的。下面在具体代码中进行说明,注意阅读注释说明!
默认key:
keyGenerator生成key:
编写配置类、定制化key生成器:
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
public class MyCachingConfigurer extends CachingConfigurerSupport {
/**
* 定制化key生成器
*
* 设置 全限定类名 + 方法名 + 参数名 共同组成 key
*
* @return key生成器
* @date 2019/4/12 14:09
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (Object target, Method method, Object... params) -> {
StringBuilder sb = new StringBuilder(16);
sb.append(target.getClass().getName());
sb.append("_");
sb.append(method.getName());
sb.append("_");
for (int i = 0; i < params.length; i++) {
sb.append(params[i]);
if (i < params.length - 1) {
sb.append(",");
}
}
return sb.toString();
};
}
}
此时,若使用缓存注解时不指定key属性,那么就会默认采用Key生成器生成的注解:
主动指定key:
condition:
在激活注解功能前,进行condition验证,如果condition结果为true,则表明验证通过,缓存注解生效;否则缓存注解不生效。
condition作用时机在:缓存注解检查缓存中是否有对应的key-value 之前。注:缓存注解检查缓存中是否有对应的key-value 在 运行目标方法之前,所以 condition作用时机也在运行目标方法之前。
实验示例:
cacheNames:
通过cacheNames对数据进行隔离,不同cacheName下可以有相同的key。也可称呼cacheName为命名空间。
下面验证的是:当同时制定多个cacheName时,从哪一个cacheName取数据。
这里先给出结论:
若属性cacheNames(或属性value)指定了多个命名空间,当进行缓存存储时:
- 会在这些命名空间下都存一份key-value;
- 当进行缓存读取时,会按照cacheNames值里命名空间的顺序,挨个挨个从命名空间中查找对应的key,如果在某个命名空间中查找打了对应的缓存,就不会再查找排在后面的命名空间,也不会再执行对应方法,直接返回缓存中的value值。
实验示例:
unless:
功能是:是否令注解(在方法执行后的功能)不生效;若unless的结果为true,则(方法执行后的功能)不生效;若unless的结果为false,则(方法执行后的)功能生效。
注:unless默认为"",即相当于默认为false。
unless的作用时机:目标方法运行后。
注:如果(因为直接从缓存中获取到了数据,而导致)目标方法没有被执行,那么unless字段不生效。
举例说明一:
对于@Cacheable注解,在执行目标方法前,如果从缓存中查询到了数据,那么直接返回缓存中的数据;如果从 缓存中没有查询到数据,那么执行目标方法,目标方法执行完毕之后,判断unless的结果,若unless的结果为true,那么不缓存方法的返回值;若unless的结果为false,那么缓存方法的返回值。
举例说明二:
对于@CachePut注解,在目标方法执行完毕之后,判断unless的结果,若unless的结果为true,那么不缓存方法的返回值;若unless的结果为false,那么缓存方法的返回值。
注:因为unless的作用时机是在方法运行完毕后,所以我们可以用SpEL表达式#result 来获取方法的返回值。
实验示例:
allEntries:
此属性主要出现在@CacheEvict注解中,表示是否清除指定命名空间中的所有数据,默认为false。
beforeInvocation:
此属性主要出现在@CacheEvict注解中,表示 是否在目标方法执行前使 此注解生效。 默认为false,即:目标方法执行完毕后此注解生效。
SpringBoot 2.x 整合 redis 做缓存并对每个缓存空间设置过期时间
注意:
最后定位到,原因在使用redis存取数据时,参数有Object类,需要关系其序列化的问题。同时,因为使用redisTemplate其本身已经进行了序列化,那在没有特殊要求的情况下,不要额外再做序列化操作,会导致重复序列化产生的类型转换异常
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// .entryTtl(Duration.ZERO)
.entryTtl(Duration.ofSeconds(15L)) //设置默认缓存15秒
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
Set<String> cacheNames = new HashSet<>();
cacheNames.add("emp");
cacheNames.add("dept");
// 对每个缓存空间应用不同的配置
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("emp", config);
configMap.put("dept", config.entryTtl(Duration.ofSeconds(30L))); //这个缓存空间30秒
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.initialCacheNames(cacheNames)// 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
.withInitialCacheConfigurations(configMap)
.build();
return cacheManager;
}