上次接到需求 优化老系统加载页面和速度 刚开始是想着优化sql的 后来老大提了一句用缓存 查阅多个论坛并没有发现有那一篇文章是介绍缓存分页的 抽了个时间给大家分享一下
名称 | 解释 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新 |
@EnableCaching | 开启基于注解的缓存 |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”) |
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
allEntries (@CacheEvict ) |
是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true) |
//如果想要了解更多的注解 可以先看下这编文章的 链接 感觉注解这一块还是整理得不错的
Spring boot 之 spring-boot-starter-cache (整合redis)_MrLee的博客-CSDN博客_spring-boot-starter-cache
<!-- Redis Client 3版本以上会报错与spring-boot-starter-data-redis冲突 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1- yml文件配置方式
spring:
thymeleaf:
cache: false
redis:
#REDIS 数据库索引(默认为0)
database: 1
#redis服务地址
host: 127.0.0.1
#redis服务连接端口
port: 6379
#redis密码(没有密码默认为空)
password:
#连接池最大连接数(使用负值表示没有限制)
jedis:
pool:
max-active: 8
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 8
# 连接池中的最大空闲连接
max-idle: 500
# 连接池最大连接数(使用负值表示没有限制)
max-active: 2000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: 10000
#连接超时时间(毫秒)
timeout: 5000
#打印sql日志
logging:
level:
root: debug
2-SpringBoot 启动加的注释
@SpringBootApplication
//扫描容器
@ComponentScan(basePackages = {"com.lwq.demo.*"})
//扫描mapper层
@MapperScan("com.lwq.demo.dao")
//启动缓存
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3- coifg 配置 单独这样配置的话也是可以正常运行的 这样的话存在redis 里面缓存的数据就是二进制的了
/**
* @author redis 配置
* 继承缓存配置
*/
@Configuration
public class redisConfig extends CachingConfigurerSupport {
}
3-1 coifg 配置 这种的话表示没有序列化跟反序列 虽然不影响正常使用 但是心里感觉总是很憋扭 这一点要注意 如果你项目以前用到缓存的 你反序列化以后如果不清缓存的话会报错 报500 这时清下缓存就好了
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
/**
* @author redis 配置
* 继承缓存配置
*/
@Configuration
public class redisConfig extends CachingConfigurerSupport {
/**
* 二进制转成json 数据
* 配置文件的配置没有用上
*
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//过期时间 可有可无 如果不写就是永久保存 看需求 这里是缓存一个小时
//建议是永久保存
config = config.entryTtl(Duration.ofHours(1));
//序列化
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//反序列化
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
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;
}
}
4-演示分页查询缓存 controller层
/**
* 分页缓存测试 分页携带数据
*/
//@RequestMapping("/test5")
//@GetMapping("/test5")
@PostMapping("/test5")
@ResponseBody
public List<Daliy> test5( QueryRequest request,String test) {
List<Daliy> daliys = daliyService.paGeTwo(request,test);
System.out.println(daliys);
return daliys;
}
4-1分页查询service 注意的如果key 值有一个为null的话会把这个null存进缓存 官方的解释容易造成 穿透 雪崩 缓存穿击 官方给出的方案不能为null 但是可以为空 我做过验证 如果为空的话这个值是不会存进缓存的这个就给大家演示了直接进入正题
//优先级最低的 这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {
/**
* 分页缓存2携带多一个参数
* @param request
* @param test
* @return
*/
//表示是否缓存空值,一般情况下是允许的。因为这涉及到缓存的三大问题:缓存穿透、缓存雪崩、缓存击穿。官方是怎么说的
@Cacheable(key = "#p0.toString() + (#p1 != null ? # p1.toString() : '')")
List<Daliy> paGeTwo(QueryRequest request, String test);
}
4-2演示分页查询缓存删除
/**
* 根据id 删除数据
*/
@PostMapping("/test6")
@ResponseBody
public Boolean test6( String id) {
int bool = daliyService.deletes(id);
System.out.println(bool);
return bool >0 ? true:false;
}
4-3 分页删除service @CacheEvict(key = "#p0",allEntries = true) 注意的是这个 allEntries = true 这表示删除DaliyService 文件的全部数据 如果单独 @CacheEvict(key = "#p0") 这样删除缓存没有效果的
//优先级最低的 这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {
//根据id 删除数据 缓存也跟着删除 allEntries 默认为 false 只会删除数据库得不会删除缓存这里要改成true
//@CacheEvict(key = "#p0")
@CacheEvict(key = "#p0",allEntries = true)
int deletes(String id);
}
4-4本来分页查询还有数据的但是 删除数据以后DliyService 文件夹数据全部被删除
4-5 分页添加数据 和更新数据 controller层 两个方法都是一样的 这里就不作过多的介绍
/**
* 根据id 更新缓存
*/
@PostMapping("/test7")
@ResponseBody
public Boolean test7( String id,String projectNo) {
int bool = daliyService.updataTest(id,projectNo);
System.out.println(bool);
return bool >0 ? true:false;
}
/**
* 创建数据
*/
@PostMapping("/test8")
@ResponseBody
public Daliy test8( @RequestBody Daliy daliy) {
int bool = daliyService.createDayliy(daliy);
System.out.println(bool);
return daliy;
}
4-6 分页添加数据 和更新数据 Service
//优先级最低的 这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {
/**
* 更新数据
* @param id id
* @param projectNo 更新得值
* @return
*/
// 无论使用更新或添加 都把缓存删除
@CacheEvict(key = "#p0",allEntries = true)
int updataTest(String id, String projectNo);
/**
* 创建数据
* @param daliy 前端返回的数据
* @return
*/
// 无论使用更新或添加 都把缓存删除
@CacheEvict(key = "#p0",allEntries = true)
int createDayliy(Daliy daliy);
}
4-7 这都是一样的结果 allEntries = true 删除你当下文件夹的全部缓存
4-8 分页缓存就是这样就搞完 其实并不难 但是我上面并没有用到官方介绍的@CachePut 这个注解 因为上面的缓存分页的用法是我个人推荐的
5上面的更新跟删除和添加没有用到 @CachePut顺便给大家演示一下
5-1 缓存查询数据controller
/**
* 根据id查询数据 缓存没有就在数据库获取
* @param id 根据id 获取数据源
* @return
*/
@PostMapping("/test9")
@ResponseBody
public Daliy test9(String id) {
Daliy daliy = daliyService.selectByid(id);
return daliy;
}
5-2 缓存查询数据server value 的意思就是重新创建一个文件夹叫名称 text 我们是根据id查询数据存进缓存之前先判断 前端传过来的id 为不为null 如果为null 容易出现雪崩 穿透 击穿 如果为空的话就不会存进缓存
/**
* 根据id 的查到的数据
* @param id
* @return
*/
//这里重新创建文件夹text 这里重新定义key
@Cacheable(value = "text",key = "#id.toString() != null ? 'user_'+#id.toString() : ''")
Daliy selectByid(String id);
5-3 @CachePut用法 更新数据缓存
/**
* 更改缓存里面的数据
* @param daliy 根据id 更改的数据
* @return
*/
@PostMapping("/test10")
@ResponseBody
public Daliy test10(@RequestBody Daliy daliy) {
System.out.println(daliy);
Daliy updataDaliy = daliyService.updataByid(daliy);
return updataDaliy;
}
5-4更新数据缓存 value在前面有介绍过 是创建文件夹的意思 这个是根据id 替换缓存里面的数据 @CachePut 是将返回的结果集替换缓存里面的去的
/**
* 更新数据
* @param daliy
* @return
*/
//CachePut 返回的数据的结果集 替换之前的缓存数据
//不能直接这样 这样容易出现 穿透 跟 雪崩 要先判断不为null 的时候 官网提供的是可以为空但不能为null
// @CachePut(value = "text",key = "#daliy.id")
//如果更新全部字段的可以忽略这句话 更新某个字段的注意这边特别容易踩坑这是将结果集返回做缓存的 我们要先跟id把这条数据全部查出来 把要改的数据替换 在返回结果集,
@CachePut(value = "text",key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
Daliy updataByid(Daliy daliy);
原本的数据查到id22的数据
更改数据 @CachePut 是替换缓存的的意思
看看对比一下上一张图片的数据
5-5 @CachePut添加数据 原理跟 更新数据是一样的 如果有缓存的替换缓存没有的话就添加缓存
/**
* 添加数据缓存 有就替换 没有就添加
* @param daliy 根据id 更改的数据
* @return
*/
@PostMapping("/test11")
@ResponseBody
public Daliy test11(@RequestBody Daliy daliy) {
System.out.println(daliy);
Daliy daliyAdd = daliyService.daliyAdd(daliy);
return daliyAdd;
}
5-6 这边要注意的 我们添加数据的时候是没有带id @CachePut 是将结果集存缓存 我们添加数据以后会有主键回填的 如何返回实体类就行了
/**
* 添加数据
* @param daliy 要添加得数据
* @return
*/
//注意 daliy.id 现在实体类添加的数据id 是为空的 添加完数据以后为会主键回填生成id的 缓存存的是结果集
@CachePut(value = "text", key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
Daliy daliyAdd(Daliy daliy);
dao层
/**
* 添加数据
*
* @param daliy 要添加得数据
* @return
*/
@Override
public Daliy daliyAdd(Daliy daliy) {
//进行了主键回填
this.daliyMapper.insert(daliy);
return daliy;
}
没有主键的 利用主键回填
6-删除数据缓存
/**
* 根据id删除数据
* @param id 根据id 删除数据
* @return
*/
@PostMapping("/test12")
@ResponseBody
public void test12(String id) {
System.out.println(id);
int states = daliyService.deleteByid(id);
}
6-1根据id删除缓存 allEntries = true 这个删除text 文件夹全部的数据的 我只要根据id删除就行了
/**
* 根据id删除数据
* @param id
* @return
*/
//allEntries = true 这里不可以加这个哦 加上这个表示这个文件夹的text 会全部删除
//下面这样写只是根据user_id 删除的
@CacheEvict(value = "text",key = "'user_'+#id")
int deleteByid(String id);
//删除id为127的 数据缓存的数据也删除了
7- 上面的演示完了但是还是有一点 有的公司分工明细创建很多文件夹 这时候就就可以用组合注解了 可以根据你的文件 删除添加替换缓存
@Caching(
//这是替换文件夹的 userCache, key id 的结果集
put = {@CachePut(value = "userCache", key = "#user.id")},
//这是删除 文件夹 allUsersCache 所有的缓存的数据
evict = {@CacheEvict(value = "allUsersCache", allEntries = true)}
)
public AppUser createUser(AppUser user) {
logger.info("创建用户start..., user.id=" + user.getId());
appuserRepository.save(user);
return user;
}
这个不给大家过多的演示 原理跟上面的是一样的