- Spring Cache의 기본 개념, 동작 원리 이해
- @EnableCaching을 통해 캐시 기능을 활성화 하는 법
- @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig 애너테이션의 역할과 사용법
- 캐시의 생성, 갱신, 삭제 과정을 코드 예제로 실습
- 각 애너테이션이 실무에서 어떤 상황에서 사용되는가?
Spring Cache 기본 개념
메서드의 실행 결과를 캐시에 저장하고, 같은 입력값으로 다시 호출될 때 DB나 외부 API 호출없이 캐시된 결과를 반환하는 기능
성능 향상과 부하 감소를 위해 매우 자주 사용되는 패턴임
1. 핵심 동작 원리
1) 메서드가 처음 호출되면, 결과가 캐시에 저장됨 - > 메서드 실행됨
2) 동일한 파라미터로 다시 호출 시, 캐시된 값을 반환함 - > 메서드 실행 x
3) 데이터 변경시 캐시를 갱신(@CachePut)하거나, 삭제(@CacheEvict)하여 일관성을 유지할 수 있음
2. Spring Cache 활성화
캐시 기능은 기본적으로 비활성되어 있으므로, 명시적으로 활성화해줘야 함
1) 설정 클래스 작성
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}
* @EnableCaching을 통해 캐시를 활성화함
* CacheConfig 안에 필요한경우, 캐시 매니저를 직접 정의할 수 있음
2) CacheManager 자동 등록
Spring Boot에서는 기본적으로 SimpleCacheManager를 자동으로 등록함
특별한 설정이 없으면 ConcurrentMapCacheManager가 사용되어, 메모리 기반 캐시가 동작함
3) 구조 예시
cachetest/
> config/
> > CacheConfig.java << 여기에서 캐시 설정을 한다
> service/
> > ProductService.java
> controller/
> > ProductController.java
주요 캐시 애너테이션
1. @Cacheable
메서드 실행 결과를 캐시에 저장하고, 동일 인자로 호출시 저장된 값을 반환하는 애너테이션
import com.b1uffer.cachetest.entity.Product;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Cacheable("products")
public Product findProductById(Long id) {
System.out.println("DB에 접근함");
return new Product(id, "노트북", 120000);
}
}
* @Cacheable("products")의 의미 : 캐시 이름이 products인 저장소에 결과를 저장함
* 동일한 ID로 다시 호출되면 DB접근 없이 캐시된 결과를 반환함(메서드 실행 x)
실행 흐름
조건부 캐싱
condition과 unless 속성을 사용하면 캐시 조건을 제어할 수 있다고 함
import com.b1uffer.cachetest.entity.Product;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Cacheable(value = "products", condition = "#id > 0", unless = "#result == null")
public Product findProductById(Long id) {
System.out.println("DB에 접근함");
return new Product(id, "노트북", 120000);
}
}
이런 느낌으로..
* condition 속성 : 캐싱을 수행할 조건을 지정함(true일때만 저장함)
* unless 속성 : 메서드를 실행하고 결과에 따라 저장 여부를 결정함
2. @CachePut
항상 메서드를 실행하고, 실행 결과를 캐시에 강제로 갱신하는 애너테이션
import com.b1uffer.cachetest.entity.Product;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
System.out.println("DB에서 제품 업데이트 후 캐시 갱신");
return product;
}
}
* CachePut에서 key 속성을 입력할 때 입력하고자 하는 값에 대한 get 메서드가 있어야한다(getId 이런것 등)
비교
| 구분 | @Cacheable | @CachePut |
| 메서드 실행 여부 | 캐시에 없을때만 실행함 | 메서드를 항상 실행함 |
| 캐시 저장 지점 | 메서드 실행 후 결과를 저장함 | 메서드 실행 후 강제로 저장함 |
| 사용 목적 | 조회 성능 향상 | 데이터 갱신 후 캐시 동기화 |
3. @CacheEvict
캐시된 데이터를 제거할 때 사용함
import com.b1uffer.cachetest.entity.Product;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
System.out.println("제품을 삭제하고 캐시를 제거함");
}
@CacheEvict(value = "products", allEntries = true)
public void clearCache() {
System.out.println("전체 캐시를 제거함");
}
}
* allEntries 속성을 활용해서 전체 캐시를 삭제할수도 있음
beforeInvocation 속성
- beforeInvocation = true > > 메서드를 실행하기 전에 캐시를 삭제함
- 기본값은 false(메서드를 실행하고 나서 삭제함)
4. @Caching
여러 캐시 동작을 묶어서 수행할 수 있는 애너테이션
import com.b1uffer.cachetest.entity.Product;
import com.b1uffer.cachetest.repository.ProductRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
private ProductRepository productRepository;
@Caching(
put = {@CachePut(value = "products", key = "#product.id")},
evict = {@CacheEvict(value = "products", key = "'list'")}
)
public Product saveProduct(Product product) {
return product;
}
@Cacheable(value = "products", key = "'list'")
public List<Product> getProducts() {
return productRepository.findAll();
}
}
* 제품을 추가하면 상세 캐시를 갱신하면서, 목록 캐시(list)는 제거하는 로직이다.
* Cacheable은 캐시DB에서 조회한 후 없으면 메서드를 실행하고 캐시를 저장하는 애너테이션
5. @CacheConfig
클래스 단위에서 공통 캐시 이름, 키 생성 정책 등을 지정할 수 있는 애너테이션
import com.b1uffer.cachetest.entity.Product;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@CacheConfig(cacheNames = "products")
public class ProductService {
}
* @CacheConfig(cacheNames = "products")라고 지정해주어 클래스 전체에 기본 캐시 이름을 설정한다
* 이를 통해 각 메서드마다 value를 중복해서 정의할 필요가 없어져서 깔끔해짐
팁
* 캐시를 너무 광범위하게 사용하면, 데이터 불일치(inconsistency)가 발생할 수 있으니 주의
* 변경이 잦은 데이터보다는 조회 빈도가 높고 변경이 드문 데이터에 적합함
* 캐시 이름(cacheNames)은 기능별로 구분하는게 좋다. (userCache, productCache, ...)
* 다중 서버 환경에서는 로컬 캐시보다 분산 캐시(Redis, Caffeine 등)를 사용하는게 일반적임
정리
| 애너테이션 | 설명 | 주요 속성 |
| @Cacheable | 결과를 캐시에 저장하고, 동일 인자라면 캐시에서 반환함 |
value, key, condition, unless |
| @CachePut | 메서드를 실행하고 캐시를 강제로 갱신함 | value, key |
| @CacheEvict | 캐시를 제거함 | value, key, allEntries, beforeInvocation |
| @Caching | 복합 캐시 동작 정의 | cacheable, put, evict |
| @CacheConfig | 클래스 단위 공통 설정 | cacheNames, keyGenerator 등 |
'Spring Boot > Cache' 카테고리의 다른 글
| 로컬 캐시 구현과 최적화 : 로컬 캐시 구현 옵션 (0) | 2026.01.12 |
|---|---|
| Spring Cache 기본 사용 : SpEL을 활용한 동적 캐시 키 (0) | 2026.01.10 |
| Spring Cache 추상화 : Spring Boot 캐시 자동 설정 (0) | 2026.01.09 |
| Spring Cache 추상화 : 캐시 동작 원리 (0) | 2026.01.09 |
| Spring Cache 추상화 : Spring Cache 핵심 컴포넌트 (0) | 2026.01.07 |