캐시를 추상화..?
- Spring Framework의 Cache 추상화 계층 구조 이해
- 다양한 캐시 구현체(Caffeine, Redis, EnCache 등)와의 독립성을 보장하는 이유
- 캐시를 일관된 방식으로 제어할 수 있는 Spring Cache API 구조
- 선언적(Declarative) 캐시 관리 방식
- 캐시 추상화를 적용함으로써 얻을 수 있는 코드 유지보수성 향상 효과
Spring Cache 추상화
1. 캐시 추상화
Spring Cache 추상화는 여러 종류의 캐시 구현체를 일관된 방식으로 사용할 수 있게 해주는 통합 계층임
개발자는 캐시 내부 동작(저장방식, 네트워크 구조 등등..)을 몰라도, 하나의 통합 인터페이스(API)를 통해 다양한 캐시를 다룰 수 있게됨
Caffeine -> EnCache -> Redis로 교체하더라도 비즈니스 코드 수정 없이 설정만 변경해주면 됨
* MongoDB를 쓸 수 있지 않을까?
2. Spring Cache 추상화의 주요 목적
| 목적 | 설명 |
| 구현체 독립성 | Cache API를 표준화하여 Caffeine, Redis, Guava 등 구현체를 쉽게 교체 가능함 |
| 일관된 캐시 접근 방식 | 동일한 애너테이션(@Cacheable, @CacheEvict 등)으로 다양한 캐시 제어 |
| 선언적 캐시 관리 | 코드 내부에서 캐시 로직을 직접 작성하지 않아도 애너테이션으로 제어 가능 |
| 코드 유지보수성 향상 | 비즈니스 로직과 캐시 로직을 분리하여 코드 가독성과 관리성 향상 |
캐시 추상화의 필요성
1. 문제상황
Spring 이전 전통적 방식에서는, 개발자가 직접 캐시 로직을 작성했음
// 전통적 캐시 관리 방식
public String getUserName(Long id) {
String key = "user:" + id; // key
// 캐시 조회 로직 직접 작성
if(localCache.containsKey(key)) {
return localCache.get(key);
}
String name = userRepository.findNameById(id);
localCache.put(key, name);
return name;
}
이 방식의 단점
* 캐시 로직이 비즈니스 로직(서비스)에 침투함 -> 코드 복잡도가 증가함
* 캐시 변경(Redis로 교체하면..)시 전체 코드 수정 필요 -> 유지보수가 어려움
2. Spring Cache 추상화 적용 후
@Cacheable(value = "userCache", key = "#id")
public String getUserName(Long id) {
return userRepository.findNameById(id);
}
Spring Cache를 사용해서 간결하게 표현 가능
내부 동작
1) 메서드 실행 전 캐시(userCache)를 확인함
2) 캐시에 존재하면 메서드 실행 없이 캐시 데이터를 반환함
3) 캐시에 없으면 실제 메서드를 실행 후 결과를 캐시에 저장함
결과적으로, 개발자는 비즈니스 로직에 집중할 수 있고 캐시 관리 코드는 스프링이 담당하게 됨
Spring Cache 추상화 구조
세 가지 주요 계층으로 구성됨
Spring Cache Abstraction
- > @Cacheable / @CacheEvict / @CachePut (Annotation Layer)
- > CacheManager(관리자)
- > - > Cache(실제 캐시 저장소)
- > - > CacheResolver(동적 캐시 결정)
- > 캐시 구현(Caffeine, Redis, EnCache 등)
구성 요소 개요
| 구성 요소 | 역할 |
| CacheManager | 캐시를 생성하고, 각 캐시 영역(Cache)을 관리함 |
| Cache | 캐시 데이터의 CRUD를 수행하는 인터페이스 |
| CacheResolver | 여러 캐시중 어떤 캐시를 사용할지 결정함 |
선언적 캐시 관리
Spring은 캐시 로직을 AOP 기반으로 자동 적용함
개발자는 복잡한 캐시 제어 코드를 작성하지 않고, 단지 애너테이션을 붙이는 것만으로 캐시를 적용할 수 있다
| 애너테이션 | 역할 |
| @Cacheable | 메서드 실행 결과를 캐시에 저장하고, 캐시 존재시 결과를 재사용함 |
| @CacheEvict | 특정 캐시 데이터를 삭제(주로 업데이트 후 사용) |
| @CachePut | 메서드 실행 결과를 항상 캐시에 저장함(강제 갱신 시 사용함) |
| @Caching | 여러 캐시 애너테이션을 결합해서 사용함 |
예제
@Service
public class UserService {
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
System.out.println("[DB 조회] 캐시에 데이터가 없어서 DB 접근 수행");
return userRepository.findById(id).orElseThrow();
}
@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
System.out.println("[Cache Evict] 사용자 정보 삭제");
userRepository.deleteById(id);
}
}
예시 결과(예상)
[DB 조회] 캐시에 데이터가 없어 DB 접근 수행
// 이후 두번째 호출부터는 캐시 Hit 수행
캐시 추상화 적용의 장점
- 코드 단순화 : 캐시 로직을 직접 작성하지 않아도 됨
- 유지보수성 향상 : 캐시 교체시 코드 수정 없이 설정만 변경 가능
- 테스트 용이성 : 캐시와 비즈니스 로직 분리로 단위 테스트가 용이함
- 유연한 확장성 : 다양한 캐시 구현체(Caffeine, Redis 등등..) 통합 가능함
팁
* 캐시 이름(value)은 도메인 단위로 명명하면 관리가 편함(예 : userCache, contentCache, productCache 등)
* key는 #id, #root.methodName 등 SpEL을 적극 활용하면 유연한 캐시 키 구성이 가능함
* 캐시와 DB의 일관성을 위해 @CacheEvict와 @CachePut을 적절히 조합해야함
* @EnableCaching 애너테이션을 @Configuration 클래스에 반드시 추가해줘야 캐시 기능이 활성화됨
정리
| 구분 | 주요 개념 | 요약 |
| Spring Cache 추상화 | 캐시 접근 통합 계층 | 캐시 구현체 교체 가능 |
| 주요 구성 요소 | CacheManager, Cache, CacheResolver | 캐시 관리 및 실행 담당 |
| 선언적 캐시 관리 | @Cacheable, @CacheEvict 등 | 코드 간결화 및 유지보수 향상 |
| 적용 효과 | 캐시 독립성, 테스트 용이성, 확장성 확보 | 다양한 환경에 적용 가능 |
'Spring Boot > Cache' 카테고리의 다른 글
| Spring Cache 추상화 : 캐시 동작 원리 (0) | 2026.01.09 |
|---|---|
| Spring Cache 추상화 : Spring Cache 핵심 컴포넌트 (0) | 2026.01.07 |
| 캐시 아키텍처와 종류 : 실무 캐시 아키텍처 설계 (0) | 2026.01.04 |
| 캐시 아키텍처와 종류 : 주요 캐시 솔루션 비교 (0) | 2026.01.02 |
| 캐시 아키텍처와 종류 : 캐시 계층 구조 (0) | 2025.12.31 |