Spring Cache 추상화 - Redis 캐시 적용



Spring의 캐시 추상화를 사용하면 비즈니스 로직을 변경하지 않고 다양한 캐시 구현체(Redis, Ehcache, Caffeine 등)를 적용할 수 있습니다.



언제 사용하나요?



  • DB 조회 결과를 캐싱하여 응답 속도 개선

  • 외부 API 호출 결과 캐싱

  • 계산 비용이 높은 연산 결과 저장

  • 세션 클러스터링, 분산 캐시 환경



의존성 추가 (Maven)


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>


Redis 설정 (application.yml)


spring:
redis:
host: localhost
port: 6379
password: yourpassword # 없으면 생략
timeout: 3000
cache:
type: redis
redis:
time-to-live: 3600000 # 1시간 (밀리초)


캐시 설정 클래스


@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 기본 TTL 1시간
.serializeKeysWith(
SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withCacheConfiguration("users",
config.entryTtl(Duration.ofMinutes(30))) // users 캐시는 30분
.withCacheConfiguration("products",
config.entryTtl(Duration.ofHours(24))) // products는 24시간
.build();
}
}


캐시 어노테이션 사용


@Service
public class UserService {

// 캐시에서 조회, 없으면 메서드 실행 후 캐시 저장
@Cacheable(value = "users", key = "#userId")
public User findById(Long userId) {
return userRepository.findById(userId).orElse(null);
}

// 메서드 실행 후 캐시 갱신
@CachePut(value = "users", key = "#user.id")
public User update(User user) {
return userRepository.save(user);
}

// 캐시 삭제
@CacheEvict(value = "users", key = "#userId")
public void delete(Long userId) {
userRepository.deleteById(userId);
}

// 해당 캐시 전체 삭제
@CacheEvict(value = "users", allEntries = true)
public void clearCache() {
// 캐시만 삭제
}
}


어노테이션 상세 설명








어노테이션설명
@Cacheable캐시 조회 → 없으면 메서드 실행 → 결과 캐시
@CachePut항상 메서드 실행 → 결과로 캐시 갱신
@CacheEvict캐시 삭제
@Caching여러 캐시 작업 조합


조건부 캐싱


// 조건이 true일 때만 캐싱
@Cacheable(value = "users", key = "#userId",
condition = "#userId > 0")
public User findById(Long userId) { ... }

// 결과가 null이 아닐 때만 캐싱
@Cacheable(value = "users", key = "#userId",
unless = "#result == null")
public User findById(Long userId) { ... }

// 복합 조건
@Cacheable(value = "users", key = "#userId",
condition = "#userId > 0",
unless = "#result.status == \047INACTIVE\047")
public User findById(Long userId) { ... }


SpEL로 동적 키 생성


// 단순 파라미터
@Cacheable(value = "users", key = "#userId")

// 객체 필드
@Cacheable(value = "users", key = "#user.id")

// 여러 파라미터 조합
@Cacheable(value = "search", key = "#keyword + \047_\047 + #page")

// 메서드명 포함
@Cacheable(value = "data", key = "#root.methodName + #param")

// 전체 인자 해시
@Cacheable(value = "data", key = "#root.args")


Redis CLI로 캐시 확인


# 모든 키 조회
redis-cli KEYS "*"

# 특정 패턴 조회
redis-cli KEYS "users::*"

# 값 조회
redis-cli GET "users::1"

# TTL 확인
redis-cli TTL "users::1"

# 캐시 삭제
redis-cli DEL "users::1"

# 패턴으로 삭제
redis-cli KEYS "users::*" | xargs redis-cli DEL


주의사항



  • 같은 클래스 내부 호출은 캐시가 적용되지 않음 (프록시 우회)

  • 캐시 키로 사용되는 객체는 equals/hashCode 구현 필요

  • Serializable 구현 필요 (또는 JSON 직렬화 설정)

  • TTL 설정으로 메모리 관리 필수