1.引入依赖
<!-- spring cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.写配置
① 自动配置了哪些
CacheAutoConfiguration类中有CacheConfigurationImportSelector ,设置选择器
static class CacheConfigurationImportSelector implements ImportSelector {
CacheConfigurationImportSelector() {
}
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for(int i = 0; i < types.length; ++i) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
getConfigurationClass(types[i]);方法静态代码块中
static {
Map<CacheType, String> mappings = new EnumMap(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName());
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());
MAPPINGS = Collections.unmodifiableMap(mappings);
}
有一条映射RedisCacheConfiguration.class
RedisCacheConfiguration类中自动配好了缓存管理器RedisCacheManager,
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet(cacheNames));
}
if (cacheProperties.getRedis().isEnableStatistics()) {
builder.enableStatistics();
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> {
customizer.customize(builder);
});
return (RedisCacheManager)cacheManagerCustomizers.customize(builder.build());
}
②我们需要写的配置
配置使用redis缓存
spring.cache.type=redis
3.测试使用缓存
@Cacheable //:触发将数据保存到缓存的操作
@CacheEvict // 触发将数据从缓存中删除的操作
@CachePut //不影响方法执行更新缓存
@Caching //组合以上多个操作
@CacheConfig //在类级别共享缓存的相同配置
- 开启缓存功能,在启动类(或者自己的配置类)上加上@EnableCaching注解
- 只需要使用注解即可完成操作,如果方法返回的结果需要缓存,只需在方法上使用注解@Cacheable
每个需要缓存数据都按照业务类型指定分区
测试可以发现第一次访问方法会执行方法,之后再访问方法不会进入方法体,数据直接由缓存中获取
4.缓存默认行为
- 如果换成中有,方法不调用
- key默认自动生成,为: 缓存的名::SimpleKey []
- 缓存的value的值,默认使用jdk序列化机制,将序列化后的数据存到redis
- 默认ttl时间: -1
5.自定义行为
@Cacheable 注解属性
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
① 指定生成缓存使用的key
使用key属性指定,接收一个SpEL
@Cacheable(value = {"category"}, key = "'level1Categorys'")
//或者
@Cacheable(value = {"category"}, key = "#root.method.name")
② 指定缓存数据的存活时间
配置文件中修改TTL,单位毫秒
spring.cache.redis.time-to-live=360000
③ 将数据保存为json格式
源码:
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(CacheProperties cacheProperties, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ClassLoader classLoader) {
return (org.springframework.data.redis.cache.RedisCacheConfiguration)redisCacheConfiguration.getIfAvailable(() -> {
return this.createConfiguration(cacheProperties, classLoader);
});
}
流程
CacheAutoConfiguration 导入->RedisCacheConfiguration自动配置了->RedisCacheManager ->初始化所有缓存->每个缓存决定使用什么配置->
如果RedisCacheConfiguration有就用已有的,如果没用就用默认的
->想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可
自定义配置类
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
// @Autowired
// CacheProperties cacheProperties;
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config = config.entryTtl()
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
//将配置文件的内容也拿来
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
6.采用失效模式写数据
用这个注解@CacheEvict
给写数据的地方加注解
@CacheEvict(value = "category", key = "'getLevel1Categorys'")
//或者 删除分区
@CacheEvict(value = "category", allEntries = true)
7.Spring Cache的不足
读模式
缓存穿透:查询一个null数据。解决: ache-null-values=true
缓存击穿:大量并发进来同时查询一个刚好过期的数据。 解决:加锁,spring Cache默认不加锁
@Cacheable(value = “category”, key = “#root.method.name”, sync = true)
可以加一把本地锁,只有get方法有锁
缓存雪崩: 大量ket同时过期。解决:加随机时间,加上过期时间
写模式
1.读写加锁
2.引入Canal 感知MySql的更新,去更新数据库
3.读多写多,直接去数据库查询就行
总结: 常规数据(读多写少,即时性、一致性要求不高的数据):完全可以使用Spring Cache,Spring Cache没有管写模式,有个过期时间就行了。要求不能太高
特殊数据:特殊设计