본문 바로가기
Develop Study/Spring 2025. 10. 22.

(25.10.22) Java Spring 에서의 Caching

업무간 단일 쓰레드 환경의 싱글톤 환역엥서 작업을 처리해야하는 로직이 필요하고 있다.

담당 업무가 아니지만 같은 프로젝트에서 개발 중인 만큼 Java Spring의 캐싱을 활용해서 개발내용을 정리하면서

충분히 활용할 수 있도록 하려고 한다. 


Java의 Caching 캐싱

  • @Cacheable 같은 Spring의 캐싱 어노테이션은 JVM 레벨의 메모리 캐싱을 기반으로 작동
    • Spring 캐시 추상화 → 캐시 구현체 → JVM 힙 메모리(Heap Memory) 순서대로 작동할 수 있도록 함
    • 따라서 Map 형태로 저장

Cache 구현체

  • @EnableCaching 등을 사용할 때 활용할 수 있는 다양한 캐싱용 구현체
구현체 저장 위치 특징
ConcurrentMapCache (기본) JVM Heap 매우 단순, 기본 Map 기반
Ehcache JVM Heap + 디스크 가능 오래된 방식
Caffeine JVM Heap 최신, 고성능, LRU/expire-after 등 지원
RedisCache 외부 메모리 서버 분산 환경
     
GuavaCache JVM Heap Google Guava 기반, TTL 지원
  • 내부 외에도 Redis, Caffeine 같은 외부 분산용 서벙서도 캐싱을 저장할 수 있음

Caching 주요 매서드

@EnableCaching

  • 애플리케이션에서 캐시 관련 어노테이션을 활성화
  • 빈(Bean)을 스캔시캐시 관련 어노테이션을 찾아 프록시를 생성해 캐싱을 활용할 수 있음

@Cacheable

  • 메서드 결과를 캐시에 저장하고, 동일 key로 호출 시에 메서드를 타는 것이 아니라 캐시에서 값을 반환
  • 기본 키는 메서드 파라미터이나 **key**를 지정
    • **KeyGenerator**를 커스텀 가능

@CacheEvict

  • 캐시 항목을 제거
    • allEntries=true 전체 캐시 삭제
    • **key**로 특정 캐시 삭제
    • **beforeInvocation=true**로 예외 발생 전 제거
    • 등등 조건부 제거 가능

@CachePut

  • 메서드를 항상 실행하면서 (캐시를 건너뛰지 않음) 결과를 캐시에 저장
  • 업데이트 작업 시 사용(DB 업데이트 후 캐시 갱신)

@Caching

  • 하나의 메서드에 여러 캐시 연산을 묶어 적용
    • ex) put 과 evict 동시에)

Caching 활용하기

  • 가정 상황
    • 사용자 정보가 1,000만 건 이상 DB에 존재하고, 사용자 데이터를 조회하고 갱신하는 상황
    • 수십만~수백만 건의 사용자 데이터 API 요청이 같은 데이터를 반복 조회
    → 너무 많은 Resource / cost 낭비

Config 설정

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();

        cacheManager.setCaches(Arrays.asList(
                new ConcurrentMapCache("userInfo")
        ));

        return cacheManager;
    }
}
  • Config 클레스에서 EnableCaching 캐시 추상화 기능을 활용
  • CacheManager를 활용해서 필요한 구현체를 Spring 컨테이너에 Bean 등록할 수 있도록 해야함
  • SimpleCacheManager을 통해 JVM 내부에서 Map 기반 캐시를 단순하게 관리로 가정
    • ConcurrentMapCache를 구현체로 해서 내부적으로 사용 가능

Service에서 User 에 대한 정보를 조회, 추가, 삭제 진행

@Service
public class test {

    private final UserRepository userRepository;

    ...

    @Cacheable(value = "userInfo", key = "#username")
    public List<User> findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @CachePut(value = "userInfo", key = "#user.username")
    public User addUser(User user) {
        return userRepository.save(user);
    }

    @CacheEvict(value = "userInfo", key = "#user.username")
    public void deleteUser(User user) {
        userRepository.deleteById(user.getId());
    }
}
  • @Cacheable 사용
    • key="#username" → 캐시 키를 username 파라미터로 설정해서 메서드 실행 결과를 캐시에 저장할 수 있음
    • 이 때, “userInfo” 이름의 캐시에 없으면 → userRepository.findByUsername(username) 실행 후 결과를 캐시에 저장
    • 존재할 시, DB 호출 없이 캐시에서 바로 반환
  • @CachePut 사용
    • 메서드를 항상 실행하고 결과를 캐시에 저장
    • DB에 새 데이터를 저장하면서, 캐시도 동시에 업데이트
      • 단, 해당 데이터가 캐시에 이미 있어도 무조건 갱신 : update의 읨미
  • @CacheEvict 사용
    • key="#user.username”로 특정 캐시값을 삭제하기 위해서 사용
    • DB에서 삭제한 데이터가 캐시에 남아 있으면 불일치 발생을 방지

출처

https://docs.spring.io/spring-boot/reference/io/caching.html

 

Caching :: Spring Boot

The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache. The caching logic is

docs.spring.io

 

 

https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html

 

Declarative Annotation-based Caching :: Spring Framework

The caching abstraction lets you use your own annotations to identify what method triggers cache population or eviction. This is quite handy as a template mechanism, as it eliminates the need to duplicate cache annotation declarations, which is especially

docs.spring.io

https://www.baeldung.com/spring-cache-tutorial

 

 


대용량 데이터 Req Res 처리 과정에서만 집중을 했지 서버단과 DB 간 대용량 데이터처리에 대해서는 무조건 Redis 처리로 생각했었다.

하지만, 간단한 JVM 메모리를 활용한 캐싱을 통해서도 (싱글톤 수준에서) 작동할 수 있도록 하면서 관련 내용을 알아둬야할 것이다.