Spring Boot Cache实现先校验缓存再查询缺失ID数据的方案
Spring Boot 批量ID查询的分段缓存实现方案
针对你提出的「批量查询学生ID时优先复用缓存,仅查缺失ID数据并更新缓存」的需求,下面是具体的Spring Boot实现方案:
1. 基础准备
1.1 依赖配置
首先确保项目中引入Spring Cache及缓存实现(这里以轻量级的Caffeine为例,也可以替换为Redis):
<!-- Spring Cache 核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Caffeine 缓存实现 --> <dependency> <groupId>com.github.benmanes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <!-- Spring Data JPA(操作数据库用) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
1.2 启用缓存
在Spring Boot启动类上添加@EnableCaching注解,开启缓存功能:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching public class StudentApplication { public static void main(String[] args) { SpringApplication.run(StudentApplication.class, args); } }
1.3 实体类调整
确保Student实体实现Serializable接口,支持缓存序列化:
import jakarta.persistence.Entity; import jakarta.persistence.Id; import java.io.Serializable; @Entity public class Student implements Serializable { @Id private Integer id; private String name; // 构造方法、Getter、Setter public Student() {} public Student(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
2. Repository层定义
保持基础的批量查询方法:
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface StudentRepository extends JpaRepository<Student, Integer> { // 批量查询ID对应的学生数据 List<Student> findByIdIn(List<Integer> ids); }
3. 缓存配置
配置Caffeine缓存的过期时间、最大容量等参数:
import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CacheConfig { @Bean public CacheManager studentCacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager("students"); cacheManager.setCaffeine(Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) // 缓存1小时后过期 .maximumSize(1000) // 最多缓存1000条数据 .recordStats()); // 可选:记录缓存统计信息 return cacheManager; } }
4. Service层核心逻辑
手动实现「缓存优先、按需查库」的逻辑,这是实现需求的关键:
import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service public class StudentService { private final StudentRepository studentRepository; private final Cache studentCache; // 构造注入缓存管理器和Repository public StudentService(StudentRepository studentRepository, CacheManager cacheManager) { this.studentRepository = studentRepository; this.studentCache = cacheManager.getCache("students"); } public List<Student> getStudentsByIds(List<Integer> ids) { // 1. 从缓存中提取已有ID对应的学生数据 Map<Integer, Student> cachedStudents = ids.stream() .map(id -> studentCache.get(id, Student.class)) .filter(student -> student != null) .collect(Collectors.toMap(Student::getId, student -> student)); // 2. 筛选出缓存中不存在的ID(需要查库的部分) List<Integer> missingIds = ids.stream() .filter(id -> !cachedStudents.containsKey(id)) .collect(Collectors.toList()); // 3. 对缺失ID执行数据库查询 List<Student> dbStudents = new ArrayList<>(); if (!missingIds.isEmpty()) { dbStudents = studentRepository.findByIdIn(missingIds); // 4. 将新查询到的数据存入缓存 dbStudents.forEach(student -> studentCache.put(student.getId(), student)); } // 5. 合并缓存数据与数据库数据,并保持传入ID的顺序(可选) return ids.stream() .map(id -> cachedStudents.getOrDefault(id, dbStudents.stream() .filter(student -> student.getId().equals(id)) .findFirst() .orElse(null))) .filter(student -> student != null) .collect(Collectors.toList()); } }
5. 关键注意事项
- 缓存粒度:以单个学生ID作为缓存Key,而非批量ID集合,这样能最大化缓存复用率
- 数据一致性:如果存在学生数据更新/删除操作,需要同步清理对应缓存,避免脏数据。比如用
@CacheEvict注解:@CacheEvict(value = "students", key = "#student.id") public Student updateStudent(Student student) { return studentRepository.save(student); } @CacheEvict(value = "students", key = "#id") public void deleteStudent(Integer id) { studentRepository.deleteById(id); } - 缓存替换:如果需要改用Redis作为分布式缓存,只需替换
CacheConfig为Redis配置,Service层逻辑无需修改 - 顺序保持:上述代码中最后一步保证了返回结果的顺序与传入的ID集合一致,适合对返回顺序有要求的场景
内容的提问来源于stack exchange,提问作者Akash Yadagouda
相关产品推荐
相关产品推荐

