Spring Cache

Soonwoo Kwon·2023년 8월 21일

Cache

cache란 자주 사용되는 데이터를 저장해서 재활용하는 기술이다.

데이터가 액세스할 때, 해당 데이터가 캐시 저장소에 존재하지 않는다면 DB를 통해 조회하여 해당 데이터를 캐시에 저장한다.
만약 해당 데이터가 존재한다면 해당 데이터를 곧바로 응답한다.

캐싱은 애플리케이션의 성능을 최적화하기 위한 유용한 기술이다.

하지만 캐싱을 적절하게 이용하지 않는다면 저조한 hit rate로 성능 향상이 크지 않을 수 있고,
잘못된 evition 로직은 데이터 불일치의 문제를 발생시킨다.

이러한 Cache를 Spring Boot에서 이용하는 방법을 소개한다.

의존성 추가

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>

캐싱 애노테이션

org.springframework.cache.annotation 에선 애노테이션을 통해 캐시를 편리하게 이용할 수 있는 방법을 제공한다.
AOP를 기반으로 메서드 실행 과정에 캐싱 기능이 적용된다.

대표적인 애노테이션인 @EnableCaching, @Cacheable, @CachePut, @CacheEvcit, @Caching가 자주 이용된다.

@EnableCaching

애플리케이션 클래스 또는 설정 클래스에 추가되며
애노테이션 기반의 캐싱을 이용하기 위해 필요한 애노테이션이다.

@EnableCaching
@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

@Cacheable

캐시 조회와 저장 모두를 수행할 수 있는 애노테이션이다.

파라미터로 전달된 key에 대한 캐시가 존재한다면 해당 캐시의 value를 리턴한다.
만약 해당 key에 대한 캐시가 존재하지 않는다면 메서드 내의 로직을 수행하며 return 되는 값을 캐시에 저장한다.

@Cacheable(value = "Book", key = "#bookId")
public Book getBook(Long bookId) {
	...
    return book;
}

해당 Book에 대한 캐시는 Redis 캐시의 기본 설정의 경우
Book::bookId 라는 이름으로 생성되고, value는 Book 객체를 직렬화 한 값이다.

캐싱되는 객체의 직렬화 방식은 CacheManager 빈을 통해 설정할 수 있다.

@Cacheable 애노테이션에 사용되는 속성값은 다음과 같다.

  • value: 캐시의 이름을 지정
  • cacheNames: value와 alias 관계로 동일 기능 수행
  • key: 캐시의 key를 지정. SpEL 표현식이 이용
  • condition: 캐시를 저장할 조건을 지정. SpEL 표현식이 이용
  • unless: 캐시를 저장하지 않을 조건을 지정. SpEL 표현식이 이용
  • keyGenerator: key를 생성하기 위한 특전 전략을 지정할. KeyGenerator 인터페이스를 이용
  • cacheManager: 캐시를 관리하는 빈을 지정. CacheManager 인터페이스의 구현체를 이용
  • cacheResolver: 캐시를 동적으로 해석하는 빈을 지정. CacheResolver 인터페이스의 구현체를 이용

@CachePut

캐시를 저장하는 기능을 수행하는 애노테이션이다.
만약 지정된 key에 대한 캐시가 존재한다면 갱신한다.

@CachePut(value = "Book", key = "#bookId")
public Book getBook(Long bookId) {
	...
    return book;
}

@CachePut 애노테이션에 사용되는 속성값은 다음과 같다.

  • value: 캐시의 이름을 지정
  • cacheNames: value와 alias 관계로 동일 기능 수행
  • key: 캐시의 key를 지정. SpEL 표현식이 이용
  • condition: 캐시를 저장할 조건을 지정. SpEL 표현식이 이용
  • unless: 캐시를 저장하지 않을 조건을 지정. SpEL 표현식이 이용
  • keyGenerator: key를 생성하기 위한 특전 전략을 지정할. KeyGenerator 인터페이스를 이용
  • cacheManager: 캐시를 관리하는 빈을 지정. CacheManager 인터페이스의 구현체를 이용
  • cacheResolver: 캐시를 동적으로 해석하는 빈을 지정. CacheResolver 인터페이스의 구현체를 이용

@CacheEvict

캐시를 삭제하는 기능을 수행하는 애노테이션이다.
지정된 key를 가진 캐시를 삭제한다.

allEntries = true 속성을 통해 지정된 이름의 모든 캐시를 삭제할 수 있다.

@CacheEvict(value = "Book", key = "#bookId")
public void deleteBook(Long bookId) {
	...
}

@CacheEvict(value = "Book", allEntries = true)
public void deleteAllBook() {
	...
}

@CacheEvict 애노테이션에 사용되는 속성값은 다음과 같다.

  • value: 캐시의 이름을 지정
  • cacheNames: value와 alias 관계로 동일 기능 수행
  • key: 캐시의 key를 지정. SpEL 표현식이 이용
  • condition: 캐시를 저장할 조건을 지정. SpEL 표현식이 이용
  • allEntries: true 인 경우 지정된 이름의 캐시를 모두 삭제
  • beforeInvocation: true인 경우 메서드 호출 전에 캐시 삭제가 수행. false인 경우 메서드 정상 동작 이후 캐시 삭제 수행
  • cacheManager: 캐시를 관리하는 빈을 지정. CacheManager 인터페이스의 구현체를 이용
  • cacheResolver: 캐시를 동적으로 해석하는 빈을 지정. CacheResolver 인터페이스의 구현체를 이용

@Caching

여러개의 캐싱 애노테이션을 사용하기 위해 활용된다.
애노테이션 내의 cacheable, put, evict 속성 내에 여러개의 애노테이션을 활용할 수 있다.

@Caching(
    cacheable = @Cacheable("books"),
    put = { @CachePut(value = "book", key = "#result.id"), @CachePut(value = "author", key = "#result.author.id") },
    evict = @CacheEvict(value = "allBooks", allEntries = true)
)
public Book findBookWithAuthor(Long bookId) {
    // 로직 구현
}

AOP 방식을 이용할 때 주의사항

보통 해당 캐싱 애노테이션이 적용된 메서드는 서비스 레이어에 구현될 가능성이 높다.

해당 애노테이션은 AOP 방식으로 동작하므로, Controller 단에서 프록시된 service 객체를 통해 메서드를 호출할 경우 정상적으로 동작하지만,
해당 애노테이션이 적용된 메서드가 service 코드 내에서 자기호출(self-invocation) 될 경우 AOP 방식이 적용되지 않은 메서드가 호출되어 의도한대로 캐싱이 동작하지 않는다.

CacheManager

캐시를 관리하기 위한 CacheManager의 다양한 구현체를 선택하여 활용할 수 있다.

대표적으로 다음 구현체들이 있다.

  • ConcurrentMapCacheManager: 메모리에 캐시 저장. ConcurrentHashMap 기반
  • SimpleCacheManager: 가장 기본적인 구현체
  • EhCacheCacheManager: ehcache 기반 캐시 구현체
  • RedisCacheManager: Redis 저장소 기반 캐시 구현체
@Configuration
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
   		// CacheManager 구현체 생성
 		...
        return cacheManager;
    }
}

0개의 댓글