SpringCache使用
1.引入依赖
引入springcache依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-boot-start-cache</artifactId>
</dependency>
使用的是redis缓存,也要引入redis缓存
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
为什么去除lettuce,参考《分布式缓存》,引入redisson控制
2.写配置
(1)自动配置用了哪些类?
CacheAutoConfiguration会导入RedisCacheConfiguration然后自动配置
RedisChacheManager
因为源码自动配置了
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
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;
}
}
3.测试使用缓存
开启缓存
@EnableCaching //开启缓存
public class GulidemoProductApplication {
public static void main(String[] args) {
SpringApplication.run (GulidemoProductApplication.class, args);
}
}
@Cacheable 触发把一个数据保存到缓存里面
@CacheEvict 触发将缓存删除的操作
@CachePut 不影响方法执行更新缓存
@Caching 组合以上多个操作
@CacheConfig 在类级别上既同一个类上共享缓存的配置
//指定缓存的名称,可以用来分区,按业务类型分区
@Cacheable("ategorys")//代表当前方法需要缓存,如果缓存中有、该方法不会被调用。如果缓存没有,执行完方法后结果会缓存进缓存
设置redis自动配置
自动配置源码
CacheAutoConfiguration
// 缓存自动配置源码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, // 看导入什么CacheConfiguration
CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
}
@Bean
public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
ObjectProvider<CacheManager> cacheManager) {
return new CacheManagerValidator(cacheProperties, cacheManager);
}
@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
super("cacheManager");
}
}
RedisCacheConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
@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(
determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return cacheManagerCustomizers.customize(builder.build());
}
配置
/**
* @author qxb
* @version 0.0.3
* @description MyCacheConfig
* @since 2021/6/9 23:28
*/
//EnableConfigurationProperties 将不入spring容器的配置加载入容器
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {
//读取EnableConfigurationProperties加载入容器的配置也可以在方法参数上传入使用如 redisCacheConfiguration(CacheProperties cacheProperties)
@Autowired
CacheProperties cacheProperties;
/*设置自定义redis缓存配置入spring容器*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(){
//设置默认配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig ();
config = config.serializeKeysWith (RedisSerializationContext.SerializationPair.fromSerializer (new StringRedisSerializer ()));
config = config.serializeValuesWith (RedisSerializationContext.SerializationPair.fromSerializer (new GenericJackson2JsonRedisSerializer ()));
//读取配置文件内的配置,取自RedisCacheConfiguration
CacheProperties.Redis redisProperties = cacheProperties.getRedis ();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
删除缓存
//简单用法
@CacheEvict(value = {"categorys"},key = "'getLevel1Categorys'")
//调用该方法会删除缓存category下的所有cache,如果要删除某个具体,用key="''"
@Override
@CacheEvict(value = {"category"},allEntries = true)
如果要清空多个缓存,用@Caching(evict={@CacheEvict(value="")})
SpringCache的不足
1)、读模式
缓存穿透:查询一个null数据。解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true
缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;
使用sync = true来解决击穿问题
@Cacheable(value = {"categorys"},key = "#root.method.name",sync = true)
缓存雪崩:大量的key同时过期。解决:加随机时间。
spring.cache.redis.time-to-live=3600000
2)、写模式:(缓存与数据库一致)
读写加锁。
引入Canal,感知到MySQL的更新去更新Redis
读多写多,直接去数据库查询就行
3)、总结:
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):
写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计
缓存雪崩:大量的key同时过期。解决:加随机时间。
spring.cache.redis.time-to-live=3600000
2)、写模式:(缓存与数据库一致)
读写加锁。
引入Canal,感知到MySQL的更新去更新Redis
读多写多,直接去数据库查询就行
3)、总结:
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):
写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计