캐시(Cache)는 자주 사용하는 데이터를 빠르게 제공하기 위해 메모리(RAM) 등에 저장해두는 기술
일반적으로 DB에서 데이터를 가져오는 것은 비용이 크고 속도가 느리다. 따라서, 캐시를 활용하면 불필요한 DB 접근을 줄이고 성능을 크게 향상시킬 수 있음
Spring Boot에서는 spring-boot-starter-cache를 사용하여 간단하게 캐싱 기능을 적용할 수 있다.
Spring Cache는 추상화된 캐싱 기능을 제공하여, 여러 가지 캐시 구현체(EhCache, Caffeine, Redis 등)를 쉽게 적용할 수 있도록 돕는다.
핵심 개념: Spring Cache는 DB에서 가져온 데이터를 일정 기간 메모리에 저장하여, 같은 요청이 들어오면 DB를 조회하지 않고 캐시에서 데이터를 제공
| 어노테이션 | 설명 |
|---|---|
@EnableCaching | Spring 캐시 기능 활성화 |
@Cacheable | 캐시를 사용할 메서드에 적용. 기존 캐시 데이터가 있으면 DB 조회를 생략 |
@CachePut | 캐시를 갱신할 때 사용. 항상 실행 후 새로운 데이터 저장 |
@CacheEvict | 캐시 데이터를 삭제할 때 사용 |
@Caching | 여러 개의 캐시 어노테이션을 함께 사용할 때 적용 |
Spring Cache를 사용하려면 Spring Boot Cache Starter를 추가해야 함
(1) 의존성 추가 (build.gradle)
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'
위에서 Caffeine 캐시 라이브러리도 추가했다. Caffeine은 성능이 뛰어난 JVM 기반 캐싱 라이브러리다.
(2) 캐시 기능 활성화
Spring Boot에서 캐시를 사용하려면 @EnableCaching을 추가해야 함
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
// 캐시 설정을 적용하는 설정 파일
}
이제 프로젝트에서 캐시 기능을 사용할 준비 끝
Spring Cache를 활용하면 자주 호출되는 메서드의 결과를 캐시에 저장하여 성능을 최적화할 수 있음
아래 코드는 캐시를 적용하지 않은 일반적인 DB 조회 코드다.
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product getProductById(Long productId) {
System.out.println("DB 조회 수행: " + productId);
return productRepository.findById(productId).orElseThrow(() -> new RuntimeException("상품을 찾을 수 없습니다."));
}
}
문제점
같은 productId로 여러 번 조회하면 DB에 계속 접근해야 함
성능 저하와 DB 부하 증가가 발생
이제 @Cacheable을 적용하여 캐시에서 데이터를 가져오도록 수정 해봄
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Cacheable(value = "products", key = "#productId")
public Product getProductById(Long productId) {
System.out.println("DB 조회 수행: " + productId);
return productRepository.findById(productId).orElseThrow(() -> new RuntimeException("상품을 찾을 수 없습니다."));
}
}
getProductById() 메서드가 호출될 때, 먼저 캐시에서 해당 데이터가 있는지 확인
캐시에 데이터가 있으면, DB를 조회하지 않고 캐시 값을 반환
캐시에 데이터가 없으면, DB에서 조회 후 결과를 캐시에 저장
실행 예제
productService.getProductById(1L); // 첫 번째 호출 -> DB 조회
productService.getProductById(1L); // 두 번째 호출 -> 캐시에서 가져옴 (DB 조회 X)
이제 같은 ID로 여러 번 조회해도 DB 부하가 발생하지 않음
@CachePut을 사용하면 항상 실행되고, 결과를 캐시에 저장함
업데이트 이후 최신 데이터를 캐시에 반영해야 할 때 사용
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
System.out.println("DB 업데이트 수행: " + product.getId());
return productRepository.save(product);
}
@CacheEvict은 특정 키 또는 전체 캐시를 삭제
데이터가 삭제되거나 변경될 때, 캐시를 삭제하여 최신 데이터를 보장할 수 있음
@CacheEvict(value = "products", key = "#productId")
public void deleteProduct(Long productId) {
System.out.println("DB 삭제 수행: " + productId);
productRepository.deleteById(productId);
}
Spring Cache는 다양한 캐시 저장소를 지원
Caffeine: JVM 내에서 동작하는 고성능 캐시 (단일 서버에서 빠름)
Redis: 분산 환경에서 사용 가능한 인메모리 캐시 (대규모 트래픽 대응 가능)
Caffeine 설정 예제 (application.yml)
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterWrite=10m
Redis 설정 예제 (application.yml)
spring:
cache:
type: redis
서비스 환경에 맞게 캐시 저장소를 선택
Spring Cache를 활용하면 DB 부하를 줄이고, 애플리케이션의 성능을 크게 향상시킬 수 있음
@Cacheable로 DB 조회를 줄이고
@CachePut, @CacheEvict로 데이터 일관성을 유지하며
Caffeine, Redis 같은 캐시 저장소를 적절히 선택하면 됨
이제 실무에서도 캐시를 적극 활용해서 빠르고 효율적인 시스템을 만들어보자.