参考资料:
参考demo
Spring cache使用介绍:
简介
Spring cache简化了数据库和缓存的同步过程,在绝大多数项目中都有应用
除redis外,Spring cache还可以整合Caffeine、EhCache等
重要注解介绍
Spring cache重要的注解如下:
| 注解 | 说明 |
| @EnableCaching | 开启缓存注解功能 |
| @Cacheable | 在方法执行前先查看缓存中是否有数据, 如果有数据,则直接返回缓存数据; 若没有数据,调用方法并将方法返回值放到缓存中 |
| @CachePut | 向缓存中存入数据 |
| @CacheEvict | 从缓存中删除数据 |
上述这些注解,key属性支持SPEL语法,用于标记入参或回参中,某个或者某几个属性
SPEL语法
PEL语法以#作为标记的开始
指定入参中的属性
| 表达式 | 例子 | |
| 参数名 | #{参数名}.{属性1}.{属性1.1}.{属性1.1.1}....... | #student.id |
| 参数位置 | #p{index}.{属性1}.{属性1.1}.{属性1.1.1}....... | #p0.id |
| 参数名(多参数或关系) | #{参数名1}.{属性1}.{属性1.1}_#{参数名2}.{属性2}.{属性2.1}_...... | #student.id_#student.name_#user.id |
| 参数位置(多参数或关系) | #p{index1}.{属性1}.{属性1.1}_#p{index2}.{属性2}.{属性2.1}_...... | #p0.id_#p0.name_#p1.id |
- 以参数名作为入口
key = "#id"

key = "#student.id"
指的是student中的id元素

key = "#result.body.id"
- 以参数的位置作为入口
通常是#p+参数的index,如:#p0表示第一个参数,#p1表示第二个参数......
key = "#p0"

key = "#p0.id"
第一个元素中的id元素

- 指定多个属性,关系为或
key = "#p0.id+'_'+#p0.name+'_'+#p0.age"
key = "#student.id+'_'+#student.name+'_'+#student.age"
指的是student中的id或name或者age属性

指定回参中的属性
| 表达式 | 例子 |
| #result.{属性1}.{属性1.1}.{属性1.1.1} | #result.body.id |
指的是返回值中的body元素中的id元素

其他常用的属性
allEntries
某个父标签下的所有子标签,如:
@CacheEvict(value = "student", allEntries = true)
删除student下的所有子标签
unless
unless : 表示满足条件则不缓存,如:
@Cacheable(value = "student",key = "#id",unless = "#result == null")

如果返回值是null则不缓存
condition
condition : 表示满足什么条件, 再进行缓存 ,如:
@Cacheable(value = "student",key = "#p0",condition = "#id.length() > 3")

如果返回值的id长度大于3才进行缓存
编写案例:
创建数据库并整合Mybatis-plus
首先创建数据库
CREATE TABLE `student` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`age` int(3) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
并且使用Mybatis或者Mybatis-Plus整合到SpringBoot中,参考网址
进行相关配置
pom中添加如下依赖:
cn.hutool
hutool-http
5.8.12
org.springframework.boot
spring-boot-starter-data-redis
application.yml中添加如下配置
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8
username: root
password: 123888
# Redis 配置
redis:
host: 192.168.14.53
port: 6379
password: null
database: 0
timeout: 10000 # 超时设置,毫秒
jedis:
pool:
max-active: 10 # 最大连接数
max-idle: 5 # 最大空闲连接数
min-idle: 1 # 最小空闲连接数
# 缓存配置(使用 Redis)
cache:
type: redis
并且添加相关的序列化、缓存规则
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.lang.reflect.Method;
import java.time.Duration;
/**
* Redis+Cache配置类
*/
@Configuration
public class RedisConfig {
/**
* 自定义key规则
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 设置RedisTemplate规则
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate redisTemplate(org.springframework.data.redis.connection.RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer()); // key序列化
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // value序列化
return template;
}
/**
* 设置CacheManager缓存规则
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
相关接口的编写
增加接口
@RequestMapping("add")
public ResponseEntity add(@RequestBody Student student) {
Student saved = addAndCache(student);
return ResponseEntity.ok(saved);
}
@CachePut(value = "student", key = "#result.id")
public Student addAndCache(Student student) {
studentService.save(student);
return student;
}
删除-单个接口
@RequestMapping("deleteById")
// 删除单个缓存
//@CacheEvict(value = "student",key = "#id")
@CacheEvict(value = "student",key = "#p0")
public ResponseEntity deleteById(@RequestParam("id")String id){
studentService.removeById(id);
return ResponseEntity.ok("删除成功");
}
删除-全部接口
@RequestMapping("deleteAll")
// 删除student下的全部缓存
@CacheEvict(value = "student", allEntries = true)
public ResponseEntity deleteAll(){
studentService.remove(null);
return ResponseEntity.ok("删除成功");
}
更新接口
@RequestMapping("update")
// 删除单个缓存
//@CacheEvict(value = "student",key = "#student.id")
@CacheEvict(value = "student",key = "#p0.id")
public ResponseEntity update(@RequestBody Student student){
studentService.updateById(student);
return ResponseEntity.ok(student);
}
查询-单个接口
@RequestMapping("getById")
@Cacheable(value = "student",key = "#id")
//@Cacheable(value = "student",key = "#p0")
//@Cacheable(value = "student",key = "#id",unless = "#result == null")
//@Cacheable(value = "student",key = "#p0",condition = "#id.length() > 3")
public ResponseEntity getById(@RequestParam("id")String id){
Student student = studentService.getById(id);
return ResponseEntity.ok(student);
}
查询-多个接口
@RequestMapping("getList")
//@Cacheable(value = "student",key = "#student.id+'_'+#student.name+'_'+#student.age")
@Cacheable(value = "student",key = "#p0.id+'_'+#p0.name+'_'+#p0.age")
public ResponseEntity> getList(@RequestBody Student student){
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Student::getId,student.getId())
.eq(Student::getName,student.getName())
.eq(Student::getAge,student.getAge());
List list = studentService.list(wrapper);
return ResponseEntity.ok(list);
}
总体的代码如下:
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.springcachedemo.entity.Student;
import com.example.springcachedemo.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@Slf4j
public class Controller {
@Autowired
private StudentService studentService;
@RequestMapping("add")
public ResponseEntity add(@RequestBody Student student) {
Student saved = addAndCache(student);
return ResponseEntity.ok(saved);
}
@CachePut(value = "student", key = "#result.id")
public Student addAndCache(Student student) {
studentService.save(student);
return student;
}
@RequestMapping("deleteById")
// 删除单个缓存
//@CacheEvict(value = "student",key = "#id")
@CacheEvict(value = "student",key = "#p0")
public ResponseEntity deleteById(@RequestParam("id")String id){
studentService.removeById(id);
return ResponseEntity.ok("删除成功");
}
@RequestMapping("deleteAll")
// 删除student下的全部缓存
@CacheEvict(value = "student", allEntries = true)
public ResponseEntity deleteAll(){
studentService.remove(null);
return ResponseEntity.ok("删除成功");
}
@RequestMapping("update")
// 删除单个缓存
//@CacheEvict(value = "student",key = "#student.id")
@CacheEvict(value = "student",key = "#p0.id")
public ResponseEntity update(@RequestBody Student student){
studentService.updateById(student);
return ResponseEntity.ok(student);
}
@RequestMapping("getById")
@Cacheable(value = "student",key = "#id")
//@Cacheable(value = "student",key = "#p0")
//@Cacheable(value = "student",key = "#id",unless = "#result == null")
//@Cacheable(value = "student",key = "#p0",condition = "#id.length() > 3")
public ResponseEntity getById(@RequestParam("id")String id){
Student student = studentService.getById(id);
return ResponseEntity.ok(student);
}
@RequestMapping("getList")
//@Cacheable(value = "student",key = "#student.id+'_'+#student.name+'_'+#student.age")
@Cacheable(value = "student",key = "#p0.id+'_'+#p0.name+'_'+#p0.age")
public ResponseEntity> getList(@RequestBody Student student){
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Student::getId,student.getId())
.eq(Student::getName,student.getName())
.eq(Student::getAge,student.getAge());
List list = studentService.list(wrapper);
return ResponseEntity.ok(list);
}
}
Spring-cache整合布隆过滤器
布隆过滤器作为防止缓存穿透或者缓存击穿的主要方案,在很多项目中都有应用
下面是基于Redission在上面的基础上做的布隆过滤器
- pom中添加Redission的依赖
org.redisson
redisson
3.21.3

- 添加Redision和布隆过滤器相关的配置
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
public static final String BLOOM_FILTER_KEY = "myBloom";
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://192.168.14.12:6379")
.setDatabase(0);
return Redisson.create(config);
}
@Bean
public RBloomFilter studentBloomFilter(RedissonClient redissonClient) {
RBloomFilter bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_KEY);
bloomFilter.tryInit(1_000_000L, 0.01); // 预估 100w 数据,误判率 1%
return bloomFilter;
}
}
- 注入布隆过滤器的判断方法
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class StudentBloomService {
@Autowired
private RBloomFilter studentBloomFilter;
/**
* 判断 studentId 是否可能存在
*/
public boolean mightExist(String id) {
log.info("bloom filter 判断 id 是否可能存在: {}", id);
return studentBloomFilter.contains(id);
}
}
- 在相关的接口中添加布隆过滤器
需要注意的是:布隆过滤器的元素,只能增加,不能删除,只能重建
添加接口改为:
@RequestMapping("add")
public ResponseEntity add(@RequestBody Student student) {
Student saved = addAndCache(student);
// 插入布隆过滤器
studentBloomFilter.add(String.valueOf(student.getId()));
return ResponseEntity.ok(saved);
}
@CachePut(value = "student", key = "#result.id")
public Student addAndCache(Student student) {
studentService.save(student);
return student;
}
查询接口改为:
@RequestMapping("getById")
//@Cacheable(value = "student",key = "#id")
//@Cacheable(value = "student",key = "#p0")
//@Cacheable(value = "student",key = "#id",unless = "#result == null")
//@Cacheable(value = "student",key = "#p0",condition = "#id.length() > 3")
@Cacheable(value = "student", key = "#id", condition = "@studentBloomService.mightExist(#id)")
public ResponseEntity getById(@RequestParam("id")String id){
Student student = studentService.getById(id);
return ResponseEntity.ok(student);
}

- 总的代码为:
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.springcachedemo.entity.Student;
import com.example.springcachedemo.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@Slf4j
@RequestMapping("bloom")
public class BloomController {
@Autowired
private StudentService studentService;
@Autowired
private RBloomFilter studentBloomFilter;
@RequestMapping("add")
public ResponseEntity add(@RequestBody Student student) {
Student saved = addAndCache(student);
// 插入布隆过滤器
studentBloomFilter.add(String.valueOf(student.getId()));
return ResponseEntity.ok(saved);
}
@CachePut(value = "student", key = "#result.id")
public Student addAndCache(Student student) {
studentService.save(student);
return student;
}
@RequestMapping("deleteById")
// 删除单个缓存
//@CacheEvict(value = "student",key = "#id")
@CacheEvict(value = "student",key = "#p0")
public ResponseEntity deleteById(@RequestParam("id")String id){
studentService.removeById(id);
// ⚠️ 注意:布隆过滤器 **不能删除**,只能重建!
return ResponseEntity.ok("删除成功");
}
@RequestMapping("deleteAll")
// 删除student下的全部缓存
@CacheEvict(value = "student", allEntries = true)
public ResponseEntity deleteAll(){
studentService.remove(null);
// ⚠️ 注意:布隆过滤器 **不能删除**,只能重建!
return ResponseEntity.ok("删除成功");
}
@RequestMapping("update")
// 删除单个缓存
//@CacheEvict(value = "student",key = "#student.id")
@CacheEvict(value = "student",key = "#p0.id")
public ResponseEntity update(@RequestBody Student student){
studentService.updateById(student);
// ⚠️ 注意:布隆过滤器 **不能删除**,只能重建!
return ResponseEntity.ok(student);
}
@RequestMapping("getById")
//@Cacheable(value = "student",key = "#id")
//@Cacheable(value = "student",key = "#p0")
//@Cacheable(value = "student",key = "#id",unless = "#result == null")
//@Cacheable(value = "student",key = "#p0",condition = "#id.length() > 3")
@Cacheable(value = "student", key = "#id", condition = "@studentBloomService.mightExist(#id)")
public ResponseEntity getById(@RequestParam("id")String id){
Student student = studentService.getById(id);
return ResponseEntity.ok(student);
}
@RequestMapping("getList")
//@Cacheable(value = "student",key = "#student.id+'_'+#student.name+'_'+#student.age")
@Cacheable(value = "student",key = "#p0.id+'_'+#p0.name+'_'+#p0.age")
public ResponseEntity> getList(@RequestBody Student student){
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Student::getId,student.getId())
.eq(Student::getName,student.getName())
.eq(Student::getAge,student.getAge());
List list = studentService.list(wrapper);
return ResponseEntity.ok(list);
}
}
浙公网安备 33010602011771号