
@EnableCaching 설정으로 캐시 기능을 활성화해야 한다.@Cacheable, @CachePut, @CacheEvict가 있다.💡 Tip
- Spring Cache는 AOP 프록시 기반이라 같은 클래스 내에서 자기 자신 메서드를 호출할 땐 적용되지 않는다. → 자기 자신을 주입받아 호출하거나, 캐시 적용 메서드를 별도 Bean으로 분리하여 해결
- Spring Cache는 TTL, 만료 정책, 최대 크기 설정을 직접 제공하지 않는다. → 구현체(Caffeine/Redis 등)에서 설정해야 함
condition, unless)key 속성에 SpEL 활용)unless = "#result == null")@Service
public class MenuService {
@Cacheable(value = "menus", key = "#id", unless = "#result == null")
public Menu getMenu(Long id) {
return menuRepository.findById(id)
.orElseThrow(() -> new MenuNotFoundException(id));
}
}
@Cacheable과 키를 동일하게 지정해야 한다. (캐시 일관성 유지)@Service
public class MenuService {
@CachePut(value = "menus", key = "#menu.id")
public Menu updateMenu(Menu menu) {
Menu updatedMenu = menuRepository.save(menu);
return updatedMenu;
}
}
allEntries = true: 캐시 전체 삭제beforeInvocation: 메서드 실행 전 캐시 제거 여부로, true로 설정하면 예외 발생 시에도 캐시가 제거됨 (기본값 : false) // 특정 캐시 항목 제거
@CacheEvict(value = "menus", key = "#id")
@Transactional
public void deleteMenu(Long id) {
menuRepository.deleteById(id);
}
// 모든 캐시 항목 제거
@CacheEvict(value = "menus", allEntries = true)
@Transactional
public void clearAllMenuCache() {
menuRepository.bulkUpdateStatus();
}
// 예외가 발생해도 캐시 무효화
@CacheEvict(value = "menus", allEntries = true, beforeInvocation = true)
public void clearAllMenuCacheForce() {
...
}
| 어노테이션 | 메서드 실행 | 캐시 동작 |
|---|---|---|
@Cacheable | 캐시 히트 시 생략 | 캐시 미스 시 저장 |
@CachePut | 항상 실행 | 항상 캐시를 갱신(덮어쓰기) |
@CacheEvict | 실행 여부와 무관 | 특정 키 또는 전체 캐시 제거 |
@Cacheable : 읽기 비중이 크고 자주 변경되지 않는 데이터@CachePut : 최신 상태를 즉시 캐시에 반영해야 하는 수정/생성 작업@CacheEvict : 데이터 삭제/벌크 변경 후 캐시 무효화💡
@Caching: 한 메서드에서 여러 캐시 동작을 조합하여 사용할 수 있다.@Caching( put = { @CachePut(value = "menus", key = "#menu.id") }, evict = { @CacheEvict(value = "menuList", allEntries = true) } ) public Menu updateAndInvalidateList(Menu menu) { return menuRepository.save(menu); }
💡 클러스터링(Clustering) : 여러 캐시 서버를 하나의 클러스터(집합)로 묶어 부하 분산과 고가용성을 제공하는 기술
| 구분 | 로컬 캐시(Local Cache) | 분산 캐시(Distributed Cache) |
|---|---|---|
| 속도 | 매우 빠름 | 상대적으로 느림 |
| 구현 난이도 | 단순 (라이브러리 추가) | 별도 캐시 서버 구축 및 운영 필요 |
| 일관성 | 서버별 불일치 발생 가능 | 여러 서버가 데이터 공유 -> 일관성 ↑ |
| 확장성 | 서버 인스턴스마다 캐시 중복 | 캐시 서버 확장/클러스터링으로 처리량 증가 |
| 장애 대응 | 앱 재시작 시 데이터 손실 | 복제/클러스터링으로 장애 대응 가능 |
| 비용 | 별도 인프라 불필요 | 추가 인프라 운영 비용 발생 |
💡 혼합 전략 (다중 레벨 캐시, L1 + L2)
- L1(로컬) : 애플리케이션 내부 초고속 응답
- L2(분산) : 서버 간 데이터 일관성 확보
- ex. Spring Boot : Caffeine(로컬, L1) + Redis(분산, L2)
- 실무에서는 혼합전략을 가장 많이 활용함