티스토리 뷰

반응형

Spring은 캐시 관련 기능을 추상화하여 편리하게 개발할 수 있도록 지원하고 있다. 이번에는 Spring이 제공하는 캐시와 관련된 기능들에 대해 알아보도록 하자.

 

 

 

1. Spring이 제공하는 캐시(Cache) 추상화


[ 캐시의 사용 ]

캐시는 서버의 부담을 줄이고, 성능을 높이기 위해 사용되는 기술이다. 예를 들어 어떤 요청을 처리하는데 계산이 복잡하거나 혹은 DB에서 조회하는게 오래 걸리는 등에 적용하여 결과를 저장해두고 가져옴으로써 빠르게 처리할 수 있다.

캐시는 값을 저장해두고 불러오기 때문에 반복적으로 동일한 결과를 반환하는 경우에 용이하다. 만약 매번 다른 결과를 돌려줘야 하는 상황에 캐시를 적용한다면 오히려 성능이 떨어지게 된다. 오히려 캐시에 저장하거나 캐시를 확인하는 작업 때문에 부하가 생기기 때문이다. 그러므로 캐시는 동일한 결과를 반환하는 반복적인 작업과 시간이 오래 걸려서 서버(애플리케이션)에 부담이 되는 경우에 적용하면 좋다.

 

 

[ Spring의 캐시(Cache) 추상화 ]

스프링은 AOP 방식으로 편리하게 메소드에 캐시 서비스를 적용하는 기능을 제공하고 있다. 캐시 서비스는 트랜잭션과 마찬가지로 AOP를 이용해 메소드 실행 과정에 투명하게 적용된다. 이를 통해 캐시 관련 로직을 핵심 비즈니스 로직으로부터 분리할 뿐만 아니라, 손쉽게 캐시 기능을 적용할 수 있다.

또한 스프링은 캐시 구현 기술에 종속되지 않도록 추상화된 서비스를 제공하고 있다. 그렇기 때문에 환경이 바뀌거나 적용할 캐시 기술을 변경하여도 애플리케이션 코드에 영향을 주지 않는다.

 

 

 

2. Spring에서 캐시 사용법(@Cacheable, @CachePut, @CacheEvict)


[ 캐시 관련 설정의 추가 ]

1. @EnableCaching 추가

Spring에서 @Cacheable과 같은 어노테이션 기반의 캐시 기능을 사용하기 위해서는 먼저 별도의 선언이 필요하다. 그렇기 때문에 @EnableCaching 어노테이션을 설정 클래스에 추가해주어야 한다.

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

 

 

2. 캐시 매니저 빈 추가

어노테이션을 추가한 후에는 캐시를 관리해줄 CacheManager를 빈으로 등록해주어야 한다. Spring은 현재 다음과 같은 캐시 매니저들을 제공하고 있다.

  • ConcurrentMapCacheManager: Java의ConcurrentHashMap을 사용해 구현한 캐시를 사용하는 캐시매니저
  • SimpleCacheManager: 기본적으로 제공하는 캐시가 없어 사용할 캐시를 직접 등록하여 사용하기 위한 캐시매니저
  • EhCacheCacheManager: 자바에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시 매니저
  • CompositeCacheManager: 1개 이상의 캐시 매니저를 사용하도록 지원해주는 혼합 캐시 매니저
  • CaffeineCacheManager: Java 8로 Guava 캐시를 재작성한 Caffeine 캐시를 사용하는 캐시 매니저
  • JCacheCacheManager: JSR-107 기반의 캐시를 사용하는 캐시 매니저

우리는 많은 캐시매니저들 중에서 필요에 맞는 캐시 매니저를 찾아서 선택하면 된다. 이와 관련된 내용은 스프링 공식 문서를 참고하면 된다.

위와 같이 캐시 매니저를 추가하면 해당 캐시 매니저가 우리의 캐시 관련 기능들을 담당하여 처리하게 된다.

 

 

 

[ @Cacheable, @CacheEvict 등을 이용한 Spring에서 캐시 사용 ]

이렇듯 Spring은 사용하기에 편리한 캐시 추상화 기능을 제공하여 애플리케이션 개발에 도움을 주고 있는데, @Cacheable과 @CachePut 그리고 @CacheEvict 어노테이션을 통해 캐시를 제어할 수 있다.

 

 

캐시를 저장/조회하기 위한 @Cacheable

클래스나 인터페이스에도 캐시를 지정할 수는 있지만 이렇게 작업하는 경우는 상당히 드물고, 보통 메소드 단위로 적용한다. 캐시를 적용할 메소드에 @Cacheable 어노테이션을 붙여주면 캐시에 데이터가 없을 경우에는 기존의 로직을 실행한 후에 캐시에 데이터를 추가하고, 캐시에 데이터가 있으면 캐시의 데이터를 반환한다.

@Cacheable("bestSeller")
public Book getBestSeller(String bookNo) {

}

 

위의 메소드는 베스트 셀러인 책을 반환하는 메소드이다. 베스트 셀러는 쉽게 변하지 않으며 많은 사용자들이 자주 찾을 것이므로 캐시를 적용하기에 적당하다.

캐시는 기본적으로 캐시 이름 하위에 key-value 형태로 데이터를 저장하는데, 위의 예제에서는 bestSeller가 캐시의 이름이고, 그 하위에 bookNo를 key 값으로 저장한다.

 

위의 해당 메소드를 여러번 호출하면 다음과 같은 순서로 진행이 된다.

  1. getBestSeller(1) 처음 호출
    1. bestSeller 캐시에 bookNo(1)에 해당하는 값이 있는지 확인한다.(값이 없음)
    2. bestSeller 캐시에 값이 없으므로 해당 로직을 실행하여 값을 반환한다.
    3. 반환한 값을 캐시에 bookNo(1)의 value로 저장한다.
  2. getBestSeller(1) 두번째 호출
    1. bestSeller 캐시에 bookNo(1)에 해당하는 값이 있는지 확인한다.(값이 있음)
    2. bestSeller 캐시에 값이 있으므로 해당 로직을 실행하지 않고 캐시에서 조회한 값을 반환한다.
  3. getBestSeller(2) 세번째 호출(bookNo가 바뀜)
    1. bestSeller 캐시에 bookNo(2)에 해당하는 값이 있는지 확인한다.(값이 없음)
    2. bestSeller 캐시에 값이 없으므로 해당 로직을 실행하여 값을 반환한다.
    3. 반환한 값을 캐시에 bookNo(2)의 value로 저장한다.

만약 메소드의 파라미터가 없다면 0이라는 디폴트 값을 Key로 사용하여 저장한다. 그리고 만약 메소드의 파라미터가 여러 개라면 파라미터들의 hashCode 값을 조합하여 키를 생성한다.

하지만 여러 개의 파라미터 중에서도 1개의 키 값으로 지정하고 싶은 경우도 있다. 그러한 경우에는 다음과 같이 Key 값을 별도로 지정해주면 된다.

@Cacheable(value = "bestSeller", key = "#bookNo")
public Book getBestSeller(String bookNo, User user, Date dateTime) {

}

 

 

Key값의 지정에는 SpEL이 사용된다. 그렇기 때문에 만약 파라미터가 객체라면 다음과 같이 하위 속성에 접근하면 된다.

@Cacheable(value = "bestSeller", key = "#book.bookNo")
public Book getBestSeller(Book book, User user, Date dateTime) {

}

 

 

만약 파라미터 값이 특정 조건인 경우에만 캐시를 적용하기를 원한다면 condition을 이용하면 된다.

@Cacheable(value = "bestSeller", key = "#book.bookNo", condition = "#user.type == 'ADMIN'")
public Book getBestSeller(Book book, User user, Date dateTime) {

}

 

 

 

캐시 저장 만을 위한 @CachePut

그 외에도 캐시에 값을 저장하는 용도로만 사용하는 @CachePut도 있다. @CachePut은 @Cacheable과 유사하게 실행 결과를 캐시에 저장하지만, 조회 시에 저장된 캐시의 내용을 사용하지는 않고 항상 메소드의 로직을 실행한다는 점에서 다르다.

 

 

 

 

캐시 제거를 위한 @CacheEvict

캐시는 적절한 시점에 제거되어야 하는데, 만약 값이 달라진다면 캐시를 제거해야 할 것이다. 그렇지 않으면 잘못된 결과가 반환된다. 캐시를 제거하는 방법은 크게 다음의 2가지가 있다.

  • 일정한 주기로 캐시를 제거
  • 값이 변할 때 캐시를 제거

 

만약 하루에 한 번씩 바뀌는 정보라면 매일 일정한 시간에 캐시를 제거하도록 하면 된다. 반면에 값이 정해진 시간에 바뀌는 것이 아니라면 값이 변할 때 캐시를 제거할 수 있다.

스프링은 캐시의 제거에도 AOP 기반으로 메소드에 적용할 수 있는 @CacheEvict 어노테이션을 제공하고 있다.

@CacheEvict에 캐시 이름을 넣어주면 메소드가 실행될 때 캐시의 내용이 제거된다. 만약 bestSeller라는 이름의 캐시를 초기화한다면 다음과 같이 작성해줄 수 있다.

@CacheEvict(value = "bestSeller")
public void clearBestSeller() {

}

 

 

@CacheEvict는 기본적으로 메소드의 키에 해당하는 캐시만 제거한다. 만약 다음과 같은 메소드에 @CacheEvict를 적용하면 bookNo와 같은 키 값을 가진 캐시만 제거된다.

@CacheEvict(value = "book", key = "#book.bookNo")
public void updateBook(Book book) {

}

 

 

만약 캐시에 저장된 값을 모두 제거할 필요가 있다면 allEntries 속성을 true로 지정해주면 된다.

@CacheEvict(value = "bestSeller", allEntires = true)
public void clearBestSeller() {

}

 

 

 

 

Spring에서 제공하는 이러한 추상화 기술은 애플리케이션 개발을 매우 용이하게 할 수 있도록 도와준다. 그러므로 관련 기술을 적극 활용하여 개발 생산성을 높이고, 핵심 로직과 공통 관심사를 분리하는 등의 이점을 얻어가도록 하자.

반응형
댓글
댓글쓰기 폼
  • sorantak 안녕하세요 좋은 글 잘 읽었습니다
    한 가지 질문이 있습니다
    @Cacheable 을 사용할 때, 캐시에 데이터가 없으면 기존의 로직을 실행한 후에 캐시에 데이터를 추가한다 까지만 말씀을 해주셨는데,
    캐시 데이터가 없을 때 캐시에 넣어주고 반환하는 데이터도 새로 저장된 캐시 데이터를 가져오는건가요? 아니면 기존 db의 데이터를 가져오는건가요?
    2021.10.07 15:40
  • 망나니개발자 안녕하세요~ 캐시는 위에 설명한대로 AOP로 동작합니다.
    그러므로 다음과 같은 구조로 진행이 되고, DB에서 조회한 데이터를 캐시에 넣은 후에 해당 데이터를 반환하는게 맞습니다:)

    1. 캐시에서 데이터 조회(없음)
    2. DB에서 데이터를 조회하고(있음)
    3. 해당 데이터를 캐시에 넣음
    2021.10.07 16:13 신고
반응형
공지사항
Total
2,673,588
Today
3,363
Yesterday
5,427
TAG
more
«   2022/07   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
글 보관함