You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 10:45:35