赞
踩
给每一个需要缓存的业务加上缓存的使用逻辑。
缓存的使用,就得考虑缓存的两种用法模式
如果每一个需要使用缓存的业务代码,都是这种编码模式,每次都这样写,要麻烦了,希望有一个简单的方式整合使用缓存。
基于此,spring
专门抽取一个叫SpringCache
的框架,是对缓存的一个抽象层。
Spring3.1
开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术并使用JCache(JSR-107)
注解简化开发;
依赖:
<!--SpringCache-->
<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>
配置:
spring:
cache:
type: redis
redis:
##存活时间,毫秒为单位
time-to-live: 100000
##key的前缀,建议不配置这个,让它默认使用分区名做前缀
key-prefix: CACHE_
##是否使用前缀
use-key-prefix: true
##是否缓存控制,防止缓存穿透
cache-null-values: true
使用:
启动类:
启动类开启缓存功能:@EnableCaching
有些情形下注解式缓存是不起作用的:同一个bean
内部方法调用,子类调用父类中有缓存注解的方法等。后者不起作用是因为缓存切面必须走代理才有效,这时可以手动使用CacheManager来获得缓存效果。
//首页接口 @GetMapping(value = {"/","index.html"}) private String indexPage(Model model) { //1、查出所有的一级分类 List<CategoryEntity> categoryEntities = categoryService.getLevel1Categorys(); //给model中放数据,就会放到页面的请求域中 model.addAttribute("categories",categoryEntities); //视图解析器会自动进行拼串 //classpath:/templates/+返回值+.html return "index"; } /** * 获取以及分类(首页用) * @return */ //value表示分区,key表示缓存的键,可以使用spel表达式 //key="'xx'" 常量 @Cacheable(value = "category",key = "#root.method.name") @Override public List<CategoryEntity> getLevel1Categorys() { long start = System.currentTimeMillis(); List<CategoryEntity> entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0)); long end = System.currentTimeMillis(); System.out.println("消耗时间:" + (end - start)); return entities; }
@Cacheable(value = “category”,key = “#root.method.name”)
代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。
如果缓存中没有,会调用方法,最后将方法的结果放入缓存
这一个注解就相当于读模式
每一个需要缓存的数据我们都来指定要放到哪个名字的缓存[缓存的分区(按照业务类型分)]
测试:访问当前接口后。redis里自动创建了缓存,以后再访问改接口,就直接走缓存了。仅仅一个注解就搞定了读模式的缓存使用,简直不要太爽
发现:
默认时间为-1
,永久;
缓存的value
值,默认使用jdk
序列化机制,将序列化后的数据存到redis
El表达式使用的例子:
SpringCache
原理:
设置key,value
的序列化:
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
RedisCacheConfiguration redisCacheConfiguration(){
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
//设置key,value的序列化机制
redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
return redisCacheConfiguration;
}
}
还是上面的接口,再次测试:发现缓存里的数据变成json
格式了
发现配置文件里的配置没生效。
修改:
@EnableConfigurationProperties({CacheProperties.class}) @Configuration @EnableCaching public class MyCacheConfig { /** * 1。原来和配置文件绑定的配置类是这样子的 * @ConfigurationProperties(prefix = "spring.cache") * 2.要让它生效,当前配置类@EnableConfigurationProperties({CacheProperties.class}), * 当前类开启属性配置的绑定功能 * 3.从容器获取CacheProperties配置,就能获取到配置文件中的所有配置了 * 4.在把配置设置上即可 * * @param cacheProperties:会自动从ioc容器中获取 * @return */ @Bean RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){ RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); //设置key,value的序列化机制 redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer())); CacheProperties.Redis redisProperties = cacheProperties.getRedis(); //设置配置文件中的配置 if (redisProperties.getTimeToLive() != null) { redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix(); } return redisCacheConfiguration; } }
数据库数据修改,删除缓存
//失效模式的使用
@CacheEvict(value = "category",key = "'getLevel1Categorys'")
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}
如果修改了数据库,要删除的缓存不知一处呢?
//同时进行多种缓存操作
@Caching(evict ={
@CacheEvict(value = "category",key = "'getLevel1Categorys'"),
@CacheEvict(value = "category",key = "'getCatalogJson'")
} )
或者
//allEntries = true指定删除category分区内的所有数据
@CacheEvict(value = "category",allEntries = true)
https://blog.csdn.net/liuyueyi25/article/details/118422143
如果希望使用自定义的key生成策略,只需继承KeyGenerator,并声明为一个bean
@Component("selfKeyGenerate")
public static class SelfKeyGenerate implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return target.getClass().getSimpleName() + "#" + method.getName() + "(" + JSON.toJSONString(params) + ")";
}
}
然后在使用的地方,利用注解中的keyGenerator来指定key生成策略
/**
* 对应的redisKey 为: get vv::ExtendDemo#selfKey([id])
*
* @param id
* @return
*/
@Cacheable(value = "vv", keyGenerator = "selfKeyGenerate")
public String selfKey(int id) {
return "selfKey:" + id + " --> " + UUID.randomUUID().toString();
}
* 4、Spring-Cache的不足之处: * 1)、读模式 * 缓存穿透:查询一个null数据。 解决方案:缓存空数据,spring-cache可以解决cache-null-values: true * 缓存击穿:大量并发进来同时查询一个正好过期的数据。 解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题,这个加的是本地锁, 高并发下,可能同一个微服务的每一个集群节点,都会去查一次数据库,有缓存之后,才都从缓存里获取数据 不过无所谓了,对数据库几乎没啥影响@Cacheable(value = "category",key = "#root.method.name",sync =true) 如果要用分布式锁Spring-Cache,做不到,得自己写 * 缓存雪崩:大量的key同时过期。 解决:加随机时间。spring-cache可以解决加上过期时间time-to-live: 100000 * 2)、写模式:(缓存与数据库一致) * 1)、读写加锁。 * 2)、引入Canal,感知到MySQL的更新去更新Redis * 3)、读多写多,直接去数据库查询就行 * * 总结: * 常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了) * 特殊数据:特殊设计 * * *
@Cacheable使用两个或多个参数作为缓存的key
常见的如分页查询:使用单引号指定分割符,最终会拼接为一个字符串
接口要包的高级一点,不然不好上缓存;通用接口入参很多,不适合用缓存
@Cacheable(key = "#page+'-'+#pageSize")
public List<User> findAllUsers(int page,int pageSize) {
int pageStart = (page-1)*pageSize;
return userMapper.findAllUsers(pageStart,pageSize);
}
入参不能太多,太多是不适合直接上缓存的,可以把接口进一步封装,针对具体的业务进一步包的高级一点,减少入参
当然还可以使用单引号自定义字符串作为缓存的key值
@Cacheable(key = "'countUsers'")
public int countUsers() {
return userMapper.countUsers();
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。