JAVA

[SpingBoot] Api 호출시 Redis를 활용한 캐시 저장

ch010104 2025. 4. 4. 19:41
  • 백엔드에서는 controller에서 Api를 호출하면, service에서 이를 처리해서 반환함.
  • 자주 호출하는 Api의 경우, 결과를 미리 다른 곳에 저장해 놓으면, 이 후에 같은 Api를 호출했을 때, 데이터베이스를 조회하지 않고 저장소에서 가져다 쓰면 되지 않을까??
  • 이는 Redis를 사용하여 key와 value의 형식으로 값을 저장해 놓음으로서 가능

  • 만약, 동아리 목록을 호출하는 Api가 있다면 Redis를 사용하여 데이터베이스 조회 수를 줄일 수 있음.(Cacheable)
  • 하지만, 동아리가 삭제가 되거나, 추가될 경우에 기존에 Redis에 저장된 정보가 최신의 정보를 반영하지 못하므로 삭제가 필요( CacheEvict ) 

1. Redis 캐시 설정

캐시를 설정하기 위해 CacheConfig 클래스를 생성하고, Redis 캐시 매니저를 설정

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))  // TTL 10분 설정
                .disableCachingNullValues()
                .serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())
                );
    }
}

 

  • 위 설정은 캐시의 TTL(Time To Live)을 10분으로 설정하고, null 값을 캐싱하지 않도록 함.
  • GenericJackson2JsonRedisSerializer를 사용하여 값을 직렬화

2. 캐시 적용 예시 – 동아리 목록

  • 사용자 대학교 기반 동아리 리스트 캐싱
  • 사용자의 대학교 ID를 기반으로 동아리 목록을 캐싱하는 방법(사용자 이름을 기반으로 하면, 사용자마다 캐시 생성)
@Service
public class ClubServiceImpl implements ClubService {

    private final UserRepository userRepository;
    private final ClubRepository clubRepository;

    public ClubServiceImpl(UserRepository userRepository, ClubRepository clubRepository) {
        this.userRepository = userRepository;
        this.clubRepository = clubRepository;
    }

    @Cacheable(
        value = "clubListByUniversity",
        key = "T(String).valueOf(@userRepository.findByUsername(#username).orElseThrow().universityId) + ':' + #limit + ':' + #offset"
    )
    @Override
    @Transactional(readOnly = true)
    public ClubListResponseDto getClubsByUserUniversity(String username, int limit, int offset) {
        // 1. 현재 로그인한 사용자 찾기
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));

        // 2. 해당 사용자의 대학교 ID로 필터링하여 동아리 조회
        List<Club> clubs = clubRepository.findByUniversity_UniversityId(user.getUniversityId());

        // 전체 동아리 개수
        int total = clubs.size();

        // 3. 페이지네이션 적용
        List<Club> paginatedClubs = clubs.stream()
                .skip(offset)
                .limit(limit)
                .collect(Collectors.toList());

        // 4. DTO 변환 후 반환
        return ClubListResponseDto.fromEntity(paginatedClubs, total, limit, offset);
    }
}
 
  • @Cacheable 어노테이션의 key 속성은 SpEL(Spring Expression Language)을 사용하여 캐시 키를 생성
  • @userRepository.findByUsername(#username).orElseThrow().universityId를 통해 사용자의 대학교 ID를 가져와서 캐시 키의 일부로 사용(UserRepository에서 이름을 userRepository로 설정 필요)
  • 이는 동일한 대학교의 사용자들이 동일한 동아리 목록 캐시를 공유

3. 캐시 삭제 (Evict)

동아리 생성, 삭제, 썸네일 변경 등 동아리 목록에 변동이 생길 때 해당 캐시를 삭제하여 최신 데이터를 유지

@CacheEvict(value = "clubListByUniversity", allEntries = true)
public Long createClub(ClubDto clubDto) {
    // 동아리 생성 로직
}
  • @CacheEvict 어노테이션의 allEntries = true 속성을 사용하여 clubListByUniversity 캐시의 모든 엔트리를 삭제

4. 캐시 만료 방식

  • 캐시는 설정된 TTL에 따라 자동으로 만료됨
  • 위의 CacheConfig 클래스에서 TTL을 10분으로 설정하였으므로, 캐시에 저장된 데이터는 10분 후 자동으로 삭제
time-to-live: 600000  # 600,000ms = 10분
  • 10분 후에는 캐시가 만료되고, 해당 메서드가 다시 실행되어 새로운 데이터로 캐시가 갱신됨.

5. 캐시 키 확인 및 삭제 (Redis CLI)

  • 프로젝트 터미널에서 redis 서버 실행
redis-cli

 

1) 키 전체 조회

  • Redis CLI를 사용하여 현재 저장된 모든 키를 조회 가능
  • Api 호출 후에 키 조회를 통해, 캐시가 저장되었는지 확인 가능(이후 호출에선, 로그를 통해서 redis에서 데이터를 가져오는 것을 확인 가능)
  • 이는  bash에서 "redis-cli" 명령어를 사용하여 redis 서버 실행 후에 가능(시스템 환경 변수 설정 필요)     
keys '*'

2) 특정 키 삭제

  • 특정 키를 삭제하여 해당 캐시를 무효화
del 'clubListByUniversity::6:12:0'