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

Spring Boot Redis缓存测试异常:findAll方法未被缓存重复调用

Redis缓存未生效:多次调用getStudents()重复触发数据库查询

我正在学习Spring Boot,搭建了一个学生信息管理的RESTful API。除了StudentConfigTest.java中的缓存测试外,其余测试均正常运行。在这个测试中,我调用studentService.getStudents()两次,预期首次调用会触发studentRepository.findAll()查询数据库并将结果存入Redis缓存,第二次调用直接从缓存返回数据,但实际studentRepository.findAll()被重复调用了多次,缓存没有生效。

以下是所有相关代码文件:

StudentConfigTest.java

package com.example.springApi.student;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
import org.springframework.cache.CacheManager;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Import;

import java.util.Arrays;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@EnableCaching
@DataRedisTest
@Import(StudentService.class)
public class StudentConfigTest {

    @MockBean
    private StudentRepository studentRepository;

    @Autowired
    private StudentService studentService;

    @Autowired
    private CacheManager cacheManager;

    @BeforeEach
    void setUp() {
        cacheManager.getCache("studentCache").clear();
    }

    @Test
    void givenRedisCaching_whenGetStudents_thenStudentsReturnedFromCache() {
        // Given
        List<Student> students = Arrays.asList(
                new Student("Alice", 25, "alice@example.com"),
                new Student("Bob", 30, "bob@example.com")
        );
        given(studentRepository.findAll()).willReturn(students);

        // When
        List<Student> studentsCacheMiss = studentService.getStudents();
        List<Student> studentsCacheHit = studentService.getStudents();

        // Then
        assertThat(studentsCacheMiss).isEqualTo(students);
        assertThat(studentsCacheHit).isEqualTo(students);

        // Verify that the repository method was called only once during cache miss
        verify(studentRepository, times(1)).findAll();

        // Additional assertion to check caching behavior
        assertThat(studentService.getStudents()).isEqualTo(students); // Cache hit again, should return the same list
    }
}

StudentService.java

@Service
public class StudentService {
    private final StudentRepository studentRepository;

    @Autowired
    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }
    @Cacheable("studentCache")
    public List<Student> getStudents() {
        return studentRepository.findAll();
    }
    public void addNewStudent(Student student){
        Optional existsEmail = studentRepository.selectExistsEmail(student.getEmail());
        if (existsEmail.isPresent()) {
            throw new IllegalStateException("Email " + student.getEmail() + " taken");
        }
        if (!isValidName(student.getName())) {
            throw new IllegalStateException("name can only contain letters");
        }

        studentRepository.save(student);
    }

    public boolean isValidName(String studentName) {
        char[] chars = studentName.toCharArray();
        System.out.println(chars);
        for (char c: chars){
            if (!Character.isLetter(c)) {
                return false;
            }
        }
        return true;
    }

    public void deleteStudent(Long studentId) {
        boolean exists = studentRepository.existsById(studentId);
        if (!exists) {
            throw new IllegalStateException("student with id " + studentId + " does not exist");
        }
        studentRepository.deleteById(studentId);

    }
}

StudentConfig.java

package com.example.springApi.student;

import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
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 java.time.Duration;

@Configuration
public class StudentConfig {
    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(60))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("studentCache",
                        RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
    }
}

StudentRepository.java

package com.example.springApi.student;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

    @Query("SELECT s FROM Student s WHERE s.email = ?1")
    Optional selectExistsEmail(String email);
}

application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/student
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
server.error.include-message=always
spring.data.redis.host=localhost
spring.data.redis.port=6379

内容的提问来源于stack exchange,提问作者jalen002

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 08:47:13