Spring에서 캐시를 사용할 때 @Cacheable 어노테이션 한 줄로 마법처럼 동작하는 배경에는 CacheManager와 Cache라는 두 핵심 인터페이스의 철저한 역할 분담이 있습니다. 이 둘의 관계를 이해하면 커스텀 캐싱 전략을 훨씬 유연하게 설계할 수 있습니다.
1. CacheManager: 캐시를 찍어내는 "공장이자 관리소"
CacheManager는 캐시 추상화의 최상위 진입점입니다. 애플리케이션 내의 모든 캐시 인스턴스를 생성하고 관리하는 역할을 합니다.
- 주요 역할:
- 캐시 생성 및 조회: "users", "products" 등 이름에 맞는 Cache 객체를 생성하거나 찾아줍니다.
- 설정의 중심: 데이터 유효 기간(TTL), 최대 크기, 직렬화 방식 등 '어떻게(How)' 저장할지를 결정합니다.
- 라이프사이클 관리: 앱 시작 시 초기화하고 종료 시 리소스를 정리합니다.
- 대표적인 구현체:
- CaffeineCacheManager: 고성능 로컬 캐시 (Caffeine 기반)
- RedisCacheManager: 분산 환경을 위한 Redis 기반 캐시
- CompositeCacheManager: 여러 매니저를 묶어 우선순위대로 처리 (Local + Remote 하이브리드)
2. Cache: 데이터가 저장되는 "실제 저장소"
Cache는 특정 이름(예: "products")을 가진 개별 저장소의 추상화된 뷰(View)입니다. 내부적으로 Map과 유사하게 Key-Value 쌍을 다룹니다.
- 주요 역할:
- CRUD 작업: 데이터를 읽고(get), 쓰고(put), 삭제(evict)하는 실질적인 연산을 수행합니다.
- 스토어 어댑터: 개발자가 Redis나 Caffeine의 API를 직접 몰라도, Spring의 공통 인터페이스로 조작할 수 있게 해줍니다.
- 데이터 격리: 각 캐시 인스턴스는 서로 독립적이라 데이터가 섞이지 않습니다.
3. 상호작용 흐름 (How they work together)
@Cacheable(value = "users", key = "#id")가 호출될 때의 내부 흐름은 다음과 같습니다.
- 요청: AOP 인터셉터가 가로채서 CacheManager에게 "이름이 users인 캐시를 달라"고 요청합니다.
- 반환: CacheManager는 설정된 방식에 맞는 Cache 객체(예: RedisCache)를 반환합니다.
- 조회/저장: 반환받은 Cache 객체에게 "키가 #id인 데이터가 있는지 확인하고, 없으면 DB 결과를 저장해"라고 명령합니다.
4. Kotlin 코드로 보는 실전 비교
어떤 캐시 전략을 사용할지 정의하는 단계입니다.
@Configuration
@EnableCaching
class CacheConfig {
@Bean
fun cacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager {
// RedisCacheManager는 '어떻게' 저장할지(예: TTL 1시간)를 결정하여
// Cache 인스턴스를 찍어내는 공장 역할을 합니다.
val configuration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // TTL 설정
.disableCachingNullValues() // null 저장 방지
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(configuration)
.build()
}
}
필요에 따라 수동으로 데이터를 조작할 때의 방식입니다.
@Service
class ManualCacheService(
private val cacheManager: CacheManager // 1. 매니저 주입
) {
fun forceClearCache() {
// 2. 매니저에게서 특정 'Cache' 인스턴스를 획득
val productCache: Cache? = cacheManager.getCache("products")
// 3. 획득한 Cache 객체로 실제 데이터 조작 (전체 삭제)
productCache?.clear()
}
fun manuallyPut(key: String, value: Any) {
val cache = cacheManager.getCache("products")
// 직접 데이터 삽입
cache?.put(key, value)
}
}
'스프링' 카테고리의 다른 글
| [Spring Boot/Kotlin] Spring Cache 어노테이션 심화 가이드 (0) | 2026.02.26 |
|---|---|
| [Spring Boot/Kotlin] Redis 캐싱: PageImpl 직렬화 에러와 ClassCastException 완벽 해결 가이드 (0) | 2026.02.17 |
| OpenFeign부터 Spring AI, 그리고 Kotlin 최적화까지: 타입 안전한 AI 서비스 구축기 (0) | 2026.02.17 |