最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

spring boot - Springboot - Redis Cache - Can not store data in cache - Stack Overflow

programmeradmin2浏览0评论

I user Docker to run the backend. In docker, I have 3 container which are bus-db, bus-server, bus-redis. This is my docker-compose.yaml

\`services:
db:
container_name: "bus-db"
image: postgis/postgis:17-3.5
ports:
\- "5432:5432"
volumes:
\- postgres:/var/lib/postgresql/data
\- ./init:/docker-entrypoint-initdb.d
environment:
POSTGRES_USER: ....
POSTGRES_PASSWORD: ....
POSTGRES_DB: postgres
healthcheck:
test: \["CMD", "pg_isready", "-U", "postgres"\]
interval: 10s
timeout: 5s
retries: 5

cache:
container_name: "bus-redis"
image: redis:7-alpine3.20
ports:
\- "6379:6379"
command: redis-server --appendonly yes --save 20 1 --loglevel warning --requirepass \<password\>
volumes:
\- cache:/data
restart: always
healthcheck:
test:
\["CMD", "redis-cli", "-a", "\<password\>", "ping"\]
interval: 10s
timeout: 5s
retries: 5

server:
container_name: "bus-backend"
build:
context: .
target: development
develop:
watch:
\- action: rebuild
path: .
ports:
\- "8080:8080" # running port
\- "8000:8000" # debugging port
environment:
SPRING_DATASOURCE_URL: "url"
SPRING_DATASOURCE_USERNAME: ...
SPRING_DATASOURCE_PASSWORD: ...
SPRING_JPA_HIBERNATE_DDL_AUTO: update
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy

volumes:
postgres:
cache:\

** This is code setup for redis in application.properties**

# Redis configuration
spring.data.redis.host=binh-duong-bus-redis
spring.data.redis.port=6379
spring.data.redis.password=<password>
spring.cache.type=redis

Then I setup the RedisConfiguration.java file like this:

@EnableCaching
@Configuration
public class RedisConfiguration {

    @Value("${spring.data.redis.host}")
    private String redisHost;

    @Value("${spring.data.redis.port}")
    private int redisPort;

    @Value("${spring.data.redis.password}")
    private String redisPassword;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
        if (redisPassword != null && !redisPassword.isEmpty()) {
            config.setPassword(redisPassword);
        }
        return new LettuceConnectionFactory(config);
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))
                .disableCachingNullValues()
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfig)
                .build();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

I used Cachable annotation in RouteServiceImpl.java like this:

@Service
public class RouteServiceImpl implements RouteService {

    private final RouteRepository routeRepository;
    private final StopRepository stopRepository;
    private final TimeLineRepository timeLineRepository;
    private final RouteMapper routeMapper;
    private final StopMapper stopMapper;
    private final TimelineMapper timelineMapper;

    public RouteServiceImpl(RouteRepository routeRepository, StopRepository stopRepository,
            TimeLineRepository timeLineRepository, RouteMapper routeMapper, StopMapper stopMapper,
            TimelineMapper timelineMapper) {
        this.routeRepository = routeRepository;
        this.stopRepository = stopRepository;
        this.timeLineRepository = timeLineRepository;
        this.routeMapper = routeMapper;
        this.stopMapper = stopMapper;
        this.timelineMapper = timelineMapper;
    }

    @Override
    @Cacheable(value = "routes", key = "'all_routes'")
    public List<RouteOnlyDto> getAllRoutesData() {
        System.out.println("CACHE MISS: Loading from database...");
        List<RouteModel> routes = routeRepository.findAll();
        if (routes.isEmpty()) {
            throw new RouteNotFound("Không có tuyến xe nào trong hệ thống");
        }
        return routeMapper.toRouteOnlyDtoList(routes);
    }

    @Override
    public ResponseEntity<ApiResponse<List<RouteOnlyDto>>> getAllRoutes() {
        List<RouteOnlyDto> routeDtos = getAllRoutesData();
        return ResponseEntity.ok(new ApiResponse<>("Danh sách tuyến đường", routeDtos));
    }

    @Override
    @Cacheable(value = "route", key = "#id")
    public RouteDto getRouteByIdData(UUID id) {
        return routeRepository.findByIdWithAllRelations(id)
                .map(routeMapper::toRouteDto)
                .orElseThrow(() -> new RouteNotFound("Tuyến đường với ID " + id + " không tồn tại"));
    }

    @Override
    public ResponseEntity<ApiResponse<RouteDto>> getRouteById(UUID id) {
        RouteDto routeDto = getRouteByIdData(id);
        return ResponseEntity.ok(new ApiResponse<>("Thông tin tuyến đường", routeDto));
    }

    @Override
    @CachePut(value = "route", key = "#result.body.data.id")
    public ResponseEntity<?> addRoute(RouteModel route) {
        RouteModel savedRoute = routeRepository.save(route);
        RouteDto responseDto = routeMapper.toRouteDto(savedRoute);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new ApiResponse<>("Tuyến đường đã được thêm thành công!", responseDto));
    }

    @Transactional
    @Override
    @CachePut(value = "route", key = "#id")
    @CacheEvict(value = "routes", allEntries = true)
    public ResponseEntity<?> updateRoute(UUID id, RouteModel updatedRoute) {
        RouteModel existingRoute = routeRepository.findById(id)
                .orElseThrow(() -> new RouteNotFound("Tuyến đường với ID " + id + " không tồn tại"));
        existingRoute.setRouteNumber(updatedRoute.getRouteNumber());
        existingRoute.setRouteName(updatedRoute.getRouteName());
        RouteModel savedRoute = routeRepository.save(existingRoute);
        RouteDto responseDto = routeMapper.toRouteDto(savedRoute);
        return ResponseEntity.ok(new ApiResponse<>("Tuyến đường đã được cập nhật thành công!", responseDto));
    }

    @Transactional
    @Override
    @CacheEvict(value = { "routes", "route", "routesByName", "stops", "timelines", "returnRoute" }, allEntries = true)
    public ResponseEntity<?> deleteRoute(UUID id) {
        RouteModel existingRoute = routeRepository.findById(id)
                .orElseThrow(() -> new RouteNotFound("Tuyến đường với ID " + id + " không tồn tại"));
        stopRepository.deleteByRouteId(id);
        timeLineRepository.deleteByRouteId(id);
        routeRepository.delete(existingRoute);
        return ResponseEntity.ok(new ApiResponse<>("Tuyến đường đã bị xóa thành công!", null));
    }

    @Override
    @Cacheable(value = "stops", key = "#routeId + #direction")
    public ResponseEntity<ApiResponse<List<StopDto>>> getRouteStops(UUID routeId, String direction) {
        List<StopModel> stops = stopRepository.findByRouteId(routeId);
        List<StopDto> stopDtos = stopMapper.toStopDtoList(stops);
        return ResponseEntity.ok(new ApiResponse<>("Danh sách điểm dừng", stopDtos));
    }

    @Override
    @Cacheable(value = "timelines", key = "#routeId + #direction")
    public ResponseEntity<ApiResponse<List<TimelineDto>>> getRouteTimelines(UUID routeId, String direction) {
        List<TimeLineModel> timeLines = timeLineRepository.findByRouteId(routeId);
        List<TimelineDto> timeLineDtos = timelineMapper.toTimelineDtoList(timeLines);
        return ResponseEntity.ok(new ApiResponse<>("Danh sách thời gian", timeLineDtos));
    }

    @Override
    @Cacheable(value = "routesByName", key = "#routeName")
    public ResponseEntity<ApiResponse<List<RouteDto>>> getRouteByRouteName(String routeName) {
        List<RouteModel> routes = routeRepository.findByRouteNameWithAllRelations(routeName);
        List<RouteDto> routeDtos = routeMapper.toRouteDtoList(routes);
        return ResponseEntity.ok(new ApiResponse<>("Danh sách tuyến đường", routeDtos));
    }

    @Override
    @Cacheable(value = "returnRoute", key = "#routeId")
    public ResponseEntity<ApiResponse<RouteDto>> getReturnRoute(UUID routeId) {
        List<RouteModel> matchingRoutes = routeRepository
                .findByRouteNumber(routeRepository.findById(routeId).get().getRouteNumber());
        RouteModel returnRoute = matchingRoutes.stream().filter(route -> !route.getId().equals(routeId)).findFirst()
                .orElseThrow(() -> new RouteNotFound("Không tìm thấy tuyến lượt về"));
        RouteDto returnRouteDto = routeMapper.toRouteDto(returnRoute);
        return ResponseEntity.ok(new ApiResponse<>("Thông tin tuyến lượt về", returnRouteDto));
    }
}

When I run api ex:http://localhost:8080/api/routes. The first time, the database will be retrieved from database (cost 4 second). But when I call that api again, it still retrieved from database but the time query has been faster. When I check redis-cli keys *. It returns empty array.

**Note: I already add @EnableCaching in the main file

Please, help me find the way to sold this prob!!! Thanks

I user Docker to run the backend. In docker, I have 3 container which are bus-db, bus-server, bus-redis. This is my docker-compose.yaml

\`services:
db:
container_name: "bus-db"
image: postgis/postgis:17-3.5
ports:
\- "5432:5432"
volumes:
\- postgres:/var/lib/postgresql/data
\- ./init:/docker-entrypoint-initdb.d
environment:
POSTGRES_USER: ....
POSTGRES_PASSWORD: ....
POSTGRES_DB: postgres
healthcheck:
test: \["CMD", "pg_isready", "-U", "postgres"\]
interval: 10s
timeout: 5s
retries: 5

cache:
container_name: "bus-redis"
image: redis:7-alpine3.20
ports:
\- "6379:6379"
command: redis-server --appendonly yes --save 20 1 --loglevel warning --requirepass \<password\>
volumes:
\- cache:/data
restart: always
healthcheck:
test:
\["CMD", "redis-cli", "-a", "\<password\>", "ping"\]
interval: 10s
timeout: 5s
retries: 5

server:
container_name: "bus-backend"
build:
context: .
target: development
develop:
watch:
\- action: rebuild
path: .
ports:
\- "8080:8080" # running port
\- "8000:8000" # debugging port
environment:
SPRING_DATASOURCE_URL: "url"
SPRING_DATASOURCE_USERNAME: ...
SPRING_DATASOURCE_PASSWORD: ...
SPRING_JPA_HIBERNATE_DDL_AUTO: update
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy

volumes:
postgres:
cache:\

** This is code setup for redis in application.properties**

# Redis configuration
spring.data.redis.host=binh-duong-bus-redis
spring.data.redis.port=6379
spring.data.redis.password=<password>
spring.cache.type=redis

Then I setup the RedisConfiguration.java file like this:

@EnableCaching
@Configuration
public class RedisConfiguration {

    @Value("${spring.data.redis.host}")
    private String redisHost;

    @Value("${spring.data.redis.port}")
    private int redisPort;

    @Value("${spring.data.redis.password}")
    private String redisPassword;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
        if (redisPassword != null && !redisPassword.isEmpty()) {
            config.setPassword(redisPassword);
        }
        return new LettuceConnectionFactory(config);
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))
                .disableCachingNullValues()
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfig)
                .build();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

I used Cachable annotation in RouteServiceImpl.java like this:

@Service
public class RouteServiceImpl implements RouteService {

    private final RouteRepository routeRepository;
    private final StopRepository stopRepository;
    private final TimeLineRepository timeLineRepository;
    private final RouteMapper routeMapper;
    private final StopMapper stopMapper;
    private final TimelineMapper timelineMapper;

    public RouteServiceImpl(RouteRepository routeRepository, StopRepository stopRepository,
            TimeLineRepository timeLineRepository, RouteMapper routeMapper, StopMapper stopMapper,
            TimelineMapper timelineMapper) {
        this.routeRepository = routeRepository;
        this.stopRepository = stopRepository;
        this.timeLineRepository = timeLineRepository;
        this.routeMapper = routeMapper;
        this.stopMapper = stopMapper;
        this.timelineMapper = timelineMapper;
    }

    @Override
    @Cacheable(value = "routes", key = "'all_routes'")
    public List<RouteOnlyDto> getAllRoutesData() {
        System.out.println("CACHE MISS: Loading from database...");
        List<RouteModel> routes = routeRepository.findAll();
        if (routes.isEmpty()) {
            throw new RouteNotFound("Không có tuyến xe nào trong hệ thống");
        }
        return routeMapper.toRouteOnlyDtoList(routes);
    }

    @Override
    public ResponseEntity<ApiResponse<List<RouteOnlyDto>>> getAllRoutes() {
        List<RouteOnlyDto> routeDtos = getAllRoutesData();
        return ResponseEntity.ok(new ApiResponse<>("Danh sách tuyến đường", routeDtos));
    }

    @Override
    @Cacheable(value = "route", key = "#id")
    public RouteDto getRouteByIdData(UUID id) {
        return routeRepository.findByIdWithAllRelations(id)
                .map(routeMapper::toRouteDto)
                .orElseThrow(() -> new RouteNotFound("Tuyến đường với ID " + id + " không tồn tại"));
    }

    @Override
    public ResponseEntity<ApiResponse<RouteDto>> getRouteById(UUID id) {
        RouteDto routeDto = getRouteByIdData(id);
        return ResponseEntity.ok(new ApiResponse<>("Thông tin tuyến đường", routeDto));
    }

    @Override
    @CachePut(value = "route", key = "#result.body.data.id")
    public ResponseEntity<?> addRoute(RouteModel route) {
        RouteModel savedRoute = routeRepository.save(route);
        RouteDto responseDto = routeMapper.toRouteDto(savedRoute);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new ApiResponse<>("Tuyến đường đã được thêm thành công!", responseDto));
    }

    @Transactional
    @Override
    @CachePut(value = "route", key = "#id")
    @CacheEvict(value = "routes", allEntries = true)
    public ResponseEntity<?> updateRoute(UUID id, RouteModel updatedRoute) {
        RouteModel existingRoute = routeRepository.findById(id)
                .orElseThrow(() -> new RouteNotFound("Tuyến đường với ID " + id + " không tồn tại"));
        existingRoute.setRouteNumber(updatedRoute.getRouteNumber());
        existingRoute.setRouteName(updatedRoute.getRouteName());
        RouteModel savedRoute = routeRepository.save(existingRoute);
        RouteDto responseDto = routeMapper.toRouteDto(savedRoute);
        return ResponseEntity.ok(new ApiResponse<>("Tuyến đường đã được cập nhật thành công!", responseDto));
    }

    @Transactional
    @Override
    @CacheEvict(value = { "routes", "route", "routesByName", "stops", "timelines", "returnRoute" }, allEntries = true)
    public ResponseEntity<?> deleteRoute(UUID id) {
        RouteModel existingRoute = routeRepository.findById(id)
                .orElseThrow(() -> new RouteNotFound("Tuyến đường với ID " + id + " không tồn tại"));
        stopRepository.deleteByRouteId(id);
        timeLineRepository.deleteByRouteId(id);
        routeRepository.delete(existingRoute);
        return ResponseEntity.ok(new ApiResponse<>("Tuyến đường đã bị xóa thành công!", null));
    }

    @Override
    @Cacheable(value = "stops", key = "#routeId + #direction")
    public ResponseEntity<ApiResponse<List<StopDto>>> getRouteStops(UUID routeId, String direction) {
        List<StopModel> stops = stopRepository.findByRouteId(routeId);
        List<StopDto> stopDtos = stopMapper.toStopDtoList(stops);
        return ResponseEntity.ok(new ApiResponse<>("Danh sách điểm dừng", stopDtos));
    }

    @Override
    @Cacheable(value = "timelines", key = "#routeId + #direction")
    public ResponseEntity<ApiResponse<List<TimelineDto>>> getRouteTimelines(UUID routeId, String direction) {
        List<TimeLineModel> timeLines = timeLineRepository.findByRouteId(routeId);
        List<TimelineDto> timeLineDtos = timelineMapper.toTimelineDtoList(timeLines);
        return ResponseEntity.ok(new ApiResponse<>("Danh sách thời gian", timeLineDtos));
    }

    @Override
    @Cacheable(value = "routesByName", key = "#routeName")
    public ResponseEntity<ApiResponse<List<RouteDto>>> getRouteByRouteName(String routeName) {
        List<RouteModel> routes = routeRepository.findByRouteNameWithAllRelations(routeName);
        List<RouteDto> routeDtos = routeMapper.toRouteDtoList(routes);
        return ResponseEntity.ok(new ApiResponse<>("Danh sách tuyến đường", routeDtos));
    }

    @Override
    @Cacheable(value = "returnRoute", key = "#routeId")
    public ResponseEntity<ApiResponse<RouteDto>> getReturnRoute(UUID routeId) {
        List<RouteModel> matchingRoutes = routeRepository
                .findByRouteNumber(routeRepository.findById(routeId).get().getRouteNumber());
        RouteModel returnRoute = matchingRoutes.stream().filter(route -> !route.getId().equals(routeId)).findFirst()
                .orElseThrow(() -> new RouteNotFound("Không tìm thấy tuyến lượt về"));
        RouteDto returnRouteDto = routeMapper.toRouteDto(returnRoute);
        return ResponseEntity.ok(new ApiResponse<>("Thông tin tuyến lượt về", returnRouteDto));
    }
}

When I run api ex:http://localhost:8080/api/routes. The first time, the database will be retrieved from database (cost 4 second). But when I call that api again, it still retrieved from database but the time query has been faster. When I check redis-cli keys *. It returns empty array.

**Note: I already add @EnableCaching in the main file

Please, help me find the way to sold this prob!!! Thanks

Share Improve this question asked Mar 18 at 7:00 ormorm 1 1
  • Your @Cacheable is useless. YOu are calling it from within the class that won't work. Caching, or rather AOP, will only work for external method calls (calls to an external class). – M. Deinum Commented Mar 18 at 7:26
Add a comment  | 

1 Answer 1

Reset to default 0

In application.properties, you set spring.data.redis.host=binh-duong-bus-redis, but in your docker-compose.yaml, the Redis service is named cache with container name bus-redis. Spring Boot can't resolve binh-duong-bus-redis.

Revise application.properties like shown below

spring.data.redis.host=bus-redis

Or add a network alias as binh-duong-bus-redis in docker-compose.yaml under the cache service

networks:
    default:
      aliases:
        - binh-duong-bus-redis

I hope it can help you fix the issue.

发布评论

评论列表(0)

  1. 暂无评论