赞
踩
spEl语法说明==>官方文档
官网springcache介绍目录:
官网的注解一共有5个,如图:
小编给他翻译下
注解 | 说明 |
---|---|
@Cacheable | 触发将数据保存到缓存的操作(启动缓存) |
@CacheEvict | 触发将数据从缓存删除的操纵(失效模式) |
@CachePut | 不影响方法执行更新缓存(双写模式) |
@Caching: | 组合以上多个操作(点击注解看源码就知道了,组合注解 ) |
@CacheConfig | 在类级别共享缓存的相同配置 |
概念图:
RedisCacheConfiguration
RedisCacheManager
流程说明:
CacheAutoConfiguration => RedisCacheConfiguration =>
自动配置了RedisCacheManager => 初始化所有的缓存 =>
每个缓存决定使用什么配置=>
=>如果RredisCacheConfiguration有就用已有的,没有就用默认配置(CacheProperties)
=>想改缓存的配置,只要给容器中放一个RredisCacheConfiguration即可
=>就会应用到当前RedisCacheManager管理的所有缓存分区中
在默认配置下,springcache给我们缓存的试用jdk序列化过的数据
我们通常是缓存Json字符串,因为使用Json能跨语言,跨平台进行交互
所以我们要修改他的默认配置,包括ttl(过期时间)、存储格式、等…
<!--spring-boot-starter-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
RedisCacheConfiguration
注意事项:要让原本配置文件的一些配置生效
开启属性绑定配置的功能
@EnableConfigurationProperties(CacheProperties.class)
MyCacheConfig
package sqy.config.cache; import org.springframework.boot.autoconfigure.cache.CacheProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @author suqinyi * @Date 2022/1/13 -更新 */ @EnableConfigurationProperties(CacheProperties.class)//开启属性绑定配置的功能 @Configuration //配置类 @EnableCaching //开启缓存功能 public class MyCacheConfig { // 第一种、从容器里面拿 // @Autowired // CacheProperties cacheProperties; /** * 配置文件中的很多东西没用上 * 1、原来和配置文件绑定的配置类是这个样子的 * @ConfigurationProperties( * prefix = "spring.cache" * ) * public class CacheProperties * * 2、要让他生效 * @EnableConfigurationProperties(CacheProperties.class)//开启属性绑定配置的功能 * */ //第二种、因为注入了容器,参数属性spring会自己去容器里面找 (CacheProperties cacheProperties) @Bean RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // config=config.entryTtl(); config= config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())); /** * GenericFastJsonRedisSerializer fastjson家族的 * GenericJackson2JsonRedisSerializer spring自带的 package org.springframework.data.redis.serializer; */ //指定序列化-Jackson config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); //指定序列化-fastjson //config= config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer())); //从所以配置中取出redis的配置 CacheProperties.Redis redisProperties = cacheProperties.getRedis(); //将配置文件中所有的配置都生效(之间从源码里面拷 RedisCacheConfiguration) 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; } }
#类型指定redis
spring.cache.type=redis
#一个小时,以毫秒为单位
spring.cache.redis.time-to-live=3600000
#给缓存的建都起一个前缀。 如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
#spring.cache.redis.key-prefix=CACHE_
#指定是否使用前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
在方法上标注注解就可以
将查询到的结果存入缓存
注意事项
01、有返回缓存
/** * TODO @Cacheable并没有单独的失效时间的方法。 * 但是可以在CacheManager配置,在+上自动刷新的功能,但是这样的的操作比较繁琐。 * 如果不设置,只有统一的过期时间很容易导致缓存雪崩的问题。 * * @Cacheable开启缓存功能 有对应的缓存就不进入方法 [需要返回值,没有返回值缓存空值] * value = "student", 【key ="#root.methodName" 或 key = "'名称'" 或 key = "#传入的参数" 或 key = "#接收参数的实体.属性" * 更多方式看spEl语法 】 * <p> * student是分区名字 * #root.methodName是spEl语法 也就是方法名 testCache * <p> * 在redis里面 他的存储就是 student::testCache * 同一个业务类型是数据放在同一个分区,树形结构, * 类如:a包里面有b,c。 b和c就是具体缓存。a就是名称空间 * @Cacheable(value = {"student"},key ="#root.method.name" ,sync = true) * sync = true 这个属性的意思是加锁,解决缓存击穿问题 */ //localhost:8080/testCache @Cacheable(value = "student", key = "#root.method.name") @GetMapping("/saveCache01") public HashMap<String, List<Student>> saveCache01() { System.out.println("方法saveCache01执行"); HashMap<String, List<Student>> map = new HashMap<String, List<Student>>(); List<Student> studentList = new ArrayList<>(); studentList.add(new Student("ssm", "888888")); studentList.add(new Student("boot", "123456")); studentList.add(new Student("cloud", "741147")); map.put("studentList", studentList); System.out.println("缓存成功"); return map; }
02、无返回值,或者返回空,缓存空值
/** * 1、返回值为void 缓存空值 * 2、返回null 缓存空值 * TODO 【NullValue】 * sr+org.springframework.cache . support.NullValue xp */ @Cacheable(value = "student", key = "#root.method.name") @GetMapping("/saveCache02") public void saveCache02() { System.out.println("方法saveCache02执行"); HashMap<String, List<Student>> map = new HashMap<String, List<Student>>(); List<Student> studentList = new ArrayList<>(); studentList.add(new Student("ssm", "888888")); studentList.add(new Student("boot", "123456")); studentList.add(new Student("cloud", "741147")); map.put("studentList", studentList); System.out.println("缓存成功"); }
简单的说:就是你执行了修改/删除的操作,他会将缓存里面数据给清除
第一种、删除单个
/**
* 失效模式(可以叫删除模式)
* value = "student",key = "'saveCache01'" 注意单引号
* student是分区名字
* saveCache01是缓存的key值。使用@Cacheable缓存的时候spEl我们指定的方法名
* todo @CacheEvict(value = "student",allEntries = true) allEntries = true表示删除student分区下所有数据
*/
@CacheEvict(value = "student", key = "'saveCache01'")//缓存 失效模式
@GetMapping("/updateData")
public void updateData() {
System.out.println("执行失效模式,删除缓存");
}
第二种、删除多个,将整个分区的缓存都清除
好比说 a下面有b和c 。将b和c一起删除
所以:同一业务\同一类型缓存的数据要放在同一的分区下面
//1、失效模式
//2、allEntries = true 删除分区所有的数据
@CacheEvict(value = "category",allEntries = true)
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
//service的业务代码
}
源码:
比如说要让哪个分区下面的哪个缓存失效(删除)
/**
* TODO @Caching 组合注解 允许在同一方法上使用多个嵌套的 @Cacheable、@CachePut和@CacheEvict
* value ==> student分区
* key ==> saveCache01 缓存的key名称
* 个人感觉还是使用@CacheEvict的删除分区里面全部的缓存方便点
*/
@Caching(evict = {
@CacheEvict(value = "student", key = "'saveCache01'"),
@CacheEvict(value = "student", key = "'saveCache02'")
})
@GetMapping("/selectEvict")
public void selectEvict() {
System.out.println("组合注解=>指定分区下失效的key");
}
/**
* @CachePut
* 根据返回值更新缓存,【双写模式】,用得比较少,这边就不搞了
*/
@CachePut
@GetMapping("/testPut")
public void testPut() {
System.out.println("双写模式");
}
@Autowired StringRedisTemplate redisTemplate;//采用string的序列化构造器 @GetMapping("/getCache") public void getCache() { HashMap<String, List<Student>> map = new HashMap<String, List<Student>>(); List<Student> studentList = new ArrayList<>(); studentList.add(new Student("ssm", "888888")); studentList.add(new Student("boot", "123456")); studentList.add(new Student("cloud", "741147")); map.put("studentList", studentList); redisTemplate.opsForValue().set("student::saveCache03", String.valueOf(map)); System.out.println("获取缓存"); String s = redisTemplate.opsForValue().get("student::saveCache03"); System.out.println(s); }
01、
02、
03、
04、
package sqy.pojo; import org.springframework.stereotype.Component; /** * @author suqinyi * @Date 2022/1/21 * user表 */ //@Component public class User { private String id;//主键 private String userName;//用户名 private String password;//密码 //get /set /构造、tostring...省略 }
package sqy.controller; import org.springframework.cache.annotation.*; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import sqy.pojo.User; import java.io.IOException; /** * @author suqinyi * @Date 2022/1/21 * springCache的实战使用【sqy测】--- TODO 缓存数据不要去数据库直接改数据!!! */ @RestController @CacheConfig(cacheNames = "user")//指定缓存空间的名称 //@Lazy//懒加载--这个有毒测试着玩,用于注入的service public class UserCacheController { /** * todo 说明 * * @Cacheable 必须要有返回【实体、list、map】-- 用于 查询 * @CachePut 必须要有返回【实体、list、map】-- 用于 新增、修改 * @CacheEvict 返回值为void--用于 删除 * @CacheConfig 配置 --通常用于指定缓存空间名称较多 * @Cacheable 组合注解 [ cacheable() 、put()、evict() ] 存、加、删 */ // private static List<User> list = new ArrayList<>(); //模拟从数据库获取到数据 private User getUserData01() { User user = new User("001", "userOO1", "123456"); return user; } //模拟从数据库获取到数据 private User getUserData02() { User user = new User("002", "userOO2", "789456"); return user; } /** * 主键查询--这个缓存是在service做,测试案例我就之间在controller写了 * 名称空间value 在controller统一指定了 * 缓存key为 名称空间::id * * @Cacheable(key = "#qvo.id",unless = "#result!=null" ) * unless = "#result!=null" 返回的结果不为空才缓存 * 这个方法不缓存空值 * localhost:8080/findById * post json {"id":"1"} */ @PostMapping("/findById") @Cacheable(key = "#qvo.id") public User findById(@RequestBody User qvo) { System.out.println("执行方法-findById"); //查到数据 if ("001".equals(qvo.getId())) { User user = getUserData01(); return user; } else { return null; } } /** * 用户名查询 * 名称空间value 在controller统一指定了 * 缓存key为 名称空间::id * 这个查询缓存空值 sr+org.springframework.cache . support.NullValue xp * localhost:8080/findByName * post json {"userName":"userOO1"} */ @PostMapping("/findByName") @Cacheable(key = "#qvo.userName") public User findByName(@RequestBody User qvo) { System.out.println("执行方法-findByName"); //查到数据 if ("userOO1".equals(qvo.getUserName())) { User user = getUserData01(); return user; } else { return null; } } /** * 新增数据-测试 @Caching组合注解 * 缓存新增的id和用户名 * condition = "#result != null" 当结果不为空时缓存 * localhost:8080/userSave * post json {"id":"002","userName":"user002"} */ @PostMapping("/userSave") @Caching(put = { @CachePut(key = "#result.id", condition = "#result != null"), @CachePut(key = "#result.userName", condition = "#result != null") }) public User userSave(@RequestBody User vo) throws IOException { if ("002".equals(vo.getId()) && "user002".equals(vo.getUserName())) { //1、存入数据库 2、查询数据返回 System.out.println(vo); return vo; } else { return null; } } /** * 修改数据-测试 @Caching组合注解--- * 【有双写模式@CachePut 和 失效模式@CacheEvict 】 * 缓存新增的id和用户名 * condition = "#result != null" 当结果不为空时缓存 * * localhost:8080/userUpdate * post json {"id":"002","userName":"user003"} */ @PostMapping("/userUpdate") @Caching(put = { @CachePut(key = "#result.id", condition = "#result != null"), @CachePut(key = "#result.userName", condition = "#result != null") }) public User userUpdate(@RequestBody User vo) { //将原本2号数据user002改成user003 if ("002".equals(vo.getId()) && "user003".equals(vo.getUserName())) { //查数据 User user = getUserData02(); //更新 user.setUserName(vo.getUserName()); user.setPassword(vo.getPassword()); return user; } else { return null; } } /** * 删除数据 * 缓存新增的id和用户名 * condition = "#result != null" 当结果不为空时缓存 * localhost:8080/userDel * post json {"id":"001","userName":"user001"} * */ @PostMapping("/userDel") @Caching(evict = { @CacheEvict(key = "#vo.id"), @CacheEvict(key = "#vo.userName") }) public void userDel(@RequestBody User vo) throws Exception { //删除1号数据 if ("001".equals(vo.getId()) && "user001".equals(vo.getUserName())) { //1、查数据 User user = getUserData01(); System.out.println(user); //2、删除 ... } else { throw new Exception("id不是1,不能删"); } } /** * 查询-有缓存就查缓存,不走方法 * localhost:8080/findUser * post josn {"id":"001","userName":"user001"} */ @PostMapping("/findUser") public void findUser(@RequestBody User vo) throws Exception { if ("001".equals(vo.getId())) { User user = this.findById(vo); System.out.println("findById==>" + user); } if ("user001".equals(vo.getUserName())) { User user = this.findByName(vo); System.out.println("findByName==>" + user); } } }
@Cacheable 代表当前方法的结果需要缓存 如果缓存种有,方法不用调用 如果缓存中没有,就会调用方法,将方法的结果放入缓存 @Cacheable(value = {"category"}) 每一个需要缓存的数据都来指定要放到那个名字缓存。[缓存的分区(推荐按照业务类型划分)] 默认行为 1、缓存中有,方法不用调用 2、key默认生成,缓存的名字::SimpleKey [](自主生成的key值) 3、缓存value的值,默认采用jdk序列化机制,将序列化后的数据存到redis 4、默认的ttl时间是 -1 代表永不过期 自定义操作: 1)、指定生成的缓存使用的key: 使用key属性指定,接收一个spEl表达式 spEl语法说明==>官方表达式地址说明:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache-spel-context 2)、指定缓存的数据的存活时间: 在配置文件中修改ttl 3)、将数据保存为json (因为json跨平台跨语言能交互) 源码类 CacheAutoConfiguration RedisCacheConfiguration
SpringCache对读模式都进行处理,解决了缓存击穿,缓存穿透,缓存雪崩的问题
但是对写模式并没有去处理
解决方法
:缓存空数据。 spring.cache.redis.use-key-prefix=true解决方法
:加锁 :默认是无加锁的; sync = true(加锁,解决缓存击穿)解决方法
:加随机时间 (很容易弄巧成拙,要注意) spring.cache.redis.time-to-live=3600000我们该如何解决(3种方式)
要加锁、加自动过期等配置
常规数据:(读多写少,即使性、一致性要求不高的的数据)
完成可以使用spring-cache,写模式(是要缓存的数据有过期时间就足够了)
特殊数据: 特殊设计
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。