Caffeine 공식 문서

Spring Cache
Spring이 제공하는 캐시 추상화
→ 어노테이션으로 처리할 수 있도록 마련됨
→ 캐시 구현체를 마련해주니 다양한 캐시로 연결이 가능
Cache 사용 셋업
Gradle 추가
// Spring Cache
implementation 'org.springframework.boot:spring-boot-starter-cache'
// caffeine local cache
implementation 'com.github.ben-manes.caffeine:caffeine'
Application 추가
@SpringBootApplication
@EnableCaching // 캐시 기능 활성화 -> 없으면 캐시 활성화 안됨
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Application.yml 추가
spring:
cache:
caffeine:
spec: maximumSize=100,expireAfterWrite=10s
# 최대 100개 캐시 유지, 10초 후 만료
# 테스트 시나리오
# 0초 -> 조회 시작 (DB 조회 + 캐시 저장)
# 5초 -> 재조회 (캐시 HIT)
# 10초 이후 -> 만료
# 11초 -> 다시 조회 (만료 감지 + DB 조회 + 캐시 저장)
CacheManager 로 설정
위와 동일한 설정이지만 따로 CacheManager 로도 설정이 가능
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS) // TTL 10초
.maximumSize(100) // 최대 1000개
);
return cacheManager;
}
}
💡
캐시 사용해보기
@Cacheable(value = "postCache", key = "'postId:' + #postId")
// 실제 캐시 저장 형태 (Cache name : postCache, Key : 1, Value : PostDto 객체 내용)
// postCache::postId:1
public PostDto getPostById(long postId) {
// 1 단계 : postId 기준으로 캐시에 값이 있는가? -> @Cacheable 이 처리해줌
// 2 단계 : 값이 있으면 바로 리턴 -> @Cacheable 이 처리해줌
// 3 단계 : 값이 없으면 DB 조회 -> 실제 구현 필요
// 4 단계 : 조회된 값을 캐시에 저장 -> @Cacheable 이 처리해줌
log.info("cache MISS , DB 조회");
Post post = postRepository.findById(postId).orElseThrow(() -> new IllegalArgumentException("없는 게시글 입니다"));
return PostDto.from(post);
}
// Post 삭제시 캐시에서도 삭제
@CacheEvict(value = "postCache", key = "#postId")
public void deletePost(long postId) {
postRepository.deleteById(postId);
}
// Post 업데이트시 캐시에서도 업데이트
@CachePut(value = "postCache", key = "#postId")
public PostDto updatePost(long postId, updatePostRequest request) {
Post post = postRepository.findById(postId).orElseThrow(() -> new IllegalArgumentException("없는 게시글 입니다"));
post.update(request);
postRepository.save(post);
return PostDto.from(post)
}

첫 조회 시는 캐시에 데이터가 없으므로 조회를 한 후 캐시에 저장함
캐시 만료 시간 동안 그 다음 부터는 조회를 해도 캐시에서 가져오므로 DB 조회를 하지 않음
같은 클래스 내부에서 어노테이션이 적용된 메서드로 호출은 Self-Invocation 으로 @Cacheable이 작동하지 않음 (AOP, @Transactional 과 동일한 문제)
@Cacheable, @CacheEvict, @CachePut, 사용 구조
(value, key) 의 구조를 띄워 사용
value → 캐시 저장소에 어디에
key → 무엇을 어떤 이름으로 저장할지 (이 때 메서드의 파라미터를 # 키워드를 통해 불러 붙일 수 있음)
@Cacheable (value = “postCache”, key = “#postId”) 을 예시로 보면 postCache 란 곳에 postId 의 값을 이름으로 하여 저장하라는 뜻
value 는 cacheNames 라고 지칭하여 사용되기도 함
@Cacheable
메서드 실행 전 캐시를 먼저 확인하고 없으면 DB 조회 후 저장
@Cacheable(cacheNames = "products", key = "#id") // (value == cacheNames)
@Transactional(readOnly = true)
public ProductResponse getProduct(Long id) {
return productRepository.findById(id).orElseThrow();
}
@CacheEvict
캐시를 삭제, 데이터가 수정/삭제 될 경우에 사용
// 특정 키만 삭제
@CacheEvict(cacheNames = "products", key = "#id")
@Transactional
public void updateProduct(Long id, ProductUpdateRequest request) {
Product product = productRepository.findById(id).orElseThrow();
product.update(request);
}
// 해당 캐시 전체 삭제
@CacheEvict(cacheNames = "products", allEntries = true)
@Transactional
public void createProduct(ProductCreateRequest request) {
productRepository.save(Product.register(request));
}
@CachePut
항상 DB를 조회 후 캐시 갱신, 캐시를 최신 상태로 유지
// 항상 DB 조회 + 캐시 갱신
// @Cacheable 과 달리 캐시 히트해도 DB 조회함
@CachePut(cacheNames = "products", key = "#id")
@Transactional
public ProductResponse updateAndRefresh(Long id, ProductUpdateRequest request) {
Product product = productRepository.findById(id).orElseThrow();
product.update(request);
return ProductResponse.from(product);
}
Share article