gony-dev 님의 블로그

[Cache] Spring Boot Cache 본문

Caching

[Cache] Spring Boot Cache

minarinamu 2025. 8. 1. 13:00
이전 포스팅에서는 Cache-Control Header를 이용한 캐싱 기능을 사용해봤다.
이번에는 서버 내부에서 Spring Boot Cache를 이용하여 캐싱 기능을 이용해보도록 하겠다.

Spring Boot Cache vs. Cache-Control Header

과연 둘의 차이는 뭘까?

아래의 표를 통해 알아보자!

  Spring Boot Cache Cache-Control Header
위치 서버 내부 클라이언트 or 중간 캐시
대상 메서드 실행 결과 HTML 응답 전체
목적 서버 처리 속도 향상 네트워크 트래픽 감소
설정 위치 Java 코드 HTTP 응답 헤더
TTL 관리 Redis TTL 사용 max-age, no-cache 등의 값으로 결정

 

결론적으로 쓰는 목적과 사용 범위가 다르기 때문에 둘을 비교하기에는 다소 애매하지만

이 둘을 함께 사용한다면 더 큰 캐싱 효과를 발휘할 수도 있다!

 


1. Spring Boot Cache Annotation

Spring 내부에서 캐싱을 사용하기 위해 사용되는 어노테이션을 알아보자!

Annotation 설명
@EnableCaching Spring Boot Cache를 사용하기 위해 캐시 활성화를 선언하는 어노테이션(Configuarion 클래스에 사용)
@CacheConfig 캐시 정보를 클래스 단위로 사용하고 관리하기 위한 어노테이션
@Cacheable 캐시 정보를 메모리 상에 저장하거나 조회해오는 기능을 수행하는 어노테이션
@CachePut 캐시 정보를 메모리 상에 저장하며 존재 시 갱신하는 기능을 수행하는 어노테이션
@CacheEvict 캐시 정보를 메모리 상에 삭제하는 기능을 수행하는 어노테이션
@Caching 여러 개의 캐시 어노테이션을 하나의 메서드에 사용할 때 사용하는 어노테이션

 

 


2. Cache Configuration

본격적으로 Spring 프로젝트에 캐싱을 적용하기 위해 사전 준비를 진행해보자!

1. build.gradle
    {
	    implementation 'org.springframework.boot:spring-boot-starter-cache'
    }​


2. CacheConfig.class
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        cacheManager.setAllowNullValues(false);
        cacheManager.setStoreByValue(false);
        cacheManager.setCacheNames(List.of("post"));
        
        return cacheManager;
    }
}​


ConcurrentMapCacheManager를 구성하는 setter 메서드들은 다음과 같다.

메서드 사용 목적
setAllowNullValues(boolean) null 값을 캐시에 저장할 수 있도록 허용할지 여부를 설정한다.
setStoreByValue(boolean) 캐시 항목을 복사해서 저장할지 여부를 설정한다.
setCacheNames(Collection<String>) 초기화 시 사용할 캐시 이름들을 설정한다.
setCacheLoader(Function<String>) 커스텀 캐시 로더를 설정한다. 원하는 방식으로 Cache 객체를 생성 가능
setRemoveCachesAfterAccess(boolean) 사용 직후 캐시를 제거할지 여부를 설정한다. (비사용 권장, 메모리 최적화 목적)

 


3. 프로젝트 적용하기

내가 프로젝트에 적용하는 플로우는 다음과 같다.
목표 - 메인 페이지에 조회되는 인기 게시글을 캐싱 적용
1. 스케줄링을 이용하여 특정 시점마다 진행되는 메서드 구현
2. 해당 스케줄링 작업 동안 데이터를 캐싱하기
3. 조회 API 호출 시, Spring Boot Cache 어노테이션을 활용하여 불필요한 데이터 호출을 줄이기
1. PostService.class
@Slf4j
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "post")
public class PostService {

    private final PostRepository postRepository;

    @CachePut(key = "#root.args[0]")
    @CacheEvict(key = "'postAll'")
    public Post createPost(PostReqDto reqDto) {
        Post post = new Post(reqDto);
        return postRepository.save(post);
    }

    @Cacheable(key = "'postAll'")
    public List<Post> getAllPosts() {
        log.info("가져오는중");
        return postRepository.findAll();
    }

    @Cacheable(key = "#id", unless = "'#result' == null")
    public Post getPostById(Long id) {
        return postRepository.findById(id).orElseThrow(RuntimeException::new);
    }

    @CachePut(key = "#root.args[0]")
    @CacheEvict(key = "'postAll'")
    @Transactional
    public Post updatePost(Long id, PostReqDto reqDto) {
        Post post = postRepository.findById(id).orElseThrow(RuntimeException::new);
        post.updatePost(reqDto);
        return post;
    }

    @Caching(evict = {
            @CacheEvict(key = "'postAll'"),
            @CacheEvict(key = "#id")
    })
    public void deletePost(Long id) {
        Post post = postRepository.findById(id).orElseThrow(RuntimeException::new);
        postRepository.delete(post);
    }
}​

2. PostController.class
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/post")
public class PostController {

    private final PostService postService;

    @PostMapping
    public ResponseEntity<?> createPost(@RequestBody PostReqDto postReqDto) {

        postService.createPost(postReqDto);
        return ResponseEntity.ok("Post created successfully");
    }

    @GetMapping
    public ResponseEntity<?> getAllPosts() {
        log.info("Get all posts");
        return ResponseEntity.ok(postService.getAllPosts());
    }

    @GetMapping("/{id}")
    public ResponseEntity<?> getPostById(@PathVariable Long id) {
        return ResponseEntity.ok(postService.getPostById(id));
    }

    @PutMapping("/{id}")
    public ResponseEntity<?> updatePost(@PathVariable Long id, @RequestBody PostReqDto postReqDto) {
        log.info("Updating post with id {}", id);
        postService.updatePost(id, postReqDto);
        return ResponseEntity.ok("Post updated successfully");
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> deletePost(@PathVariable Long id) {
        postService.deletePost(id);
        return ResponseEntity.ok("Post deleted successfully");
    }
}​

4. 검증하기

기존의 캐싱을 사용하지 않은 상태에서 getAllPost()를 반복해서 요청하면 다음과 같이 controller와 service 로직을 반복해서 실행시키는 것을 확인할 수 있다.

“Get all posts”

“가져오는중”

“Get all posts”

“가져오는중”

“Get all posts”

“가져오는중”

 

하지만 캐싱을 적용한다면 Controller Layer에서 캐싱된 결과만을 조회하는 것을 확인할 수 있다!!

 

  • 이렇게 해서 우리는 Spring Boot Cache를 사용하여
    요청이 많지만 데이터는 거의 변경되지 않는 API에 캐싱을 적용하고
    무분별한 데이터 접근을 방지하여 트래픽을 줄일 수 있다!

'Caching' 카테고리의 다른 글

[Cache] Cache-Control Header  (3) 2025.07.24
[Redis] Connection Mode-2  (0) 2024.12.08
[Redis] Connection Mode-1  (0) 2024.12.06
[Redis] Spring batch vs. Scheduler  (0) 2024.10.17
[Redis] Transaction  (1) 2024.10.15