Spring Cache, 제대로 사용하기

2022. 4. 3. 00:40Spring

Spring Cache 사용법, Annotation 등을 알아보고 설정 방식을 알아보는 것이 해당 포스팅의 목표입니다.

 

 

📌 Spring Cache Series

 

✔ Spring Cache, 제대로 사용하기

✔ Caffeine Cache, 제대로 사용하기 1

✔ Caffeine Cache, 제대로 사용하기 2

 

 

 

해당 포스팅에서는 Spring Cache에 대해 다룹니다.

Cache를 사용하기 위해서는 CacheManager가 필요한데요.

연결되는 다음 포스팅에서는 로컬 캐시 중 성능에 유리한 Caffeine Cache를 다룹니다.

 

 

 

 

 

-----------------    INDEX     -----------------

Cache ?

📌 Cache Abstraction

📌 Cache Manager

📌 의존성 추가

@EnableCaching

@Cacheable

@CacheEvict

@CachePut

@Caching

@CacheConfig

----------------------------------------------

 

 


 

Cache ?

자주 사용하는 데이터를 저장해서 재활용하는 기술

 

일반적으로 캐시는 일시적인 특징이 있는 데이터 집합을 고속 데이터 스토리지 저장합니다.

따라서 이후에 해당 데이터에 대한 요청이 있을 경우,

데이터의 원본 스토리지 위치로 액세스할 때보다 더 빠르게 요청을 처리할 수 있습니다.

이렇게 캐싱을 사용하면 이전에 검색했거나 계산한 데이터를 효율적으로 재사용할 수 있습니다.

 

부분적으로 설명하자면,

일반적으로 동일한 리소스 대해 빈번한 SELECT로 발생되는 DBMS 과부하를 줄이고자 사용합니다.

 

---

쉬운 비유를 해볼게요.

도서관에서 책을 빌리려고 "이달의 추천 책 리스트"를 확인하고 싶은데,

이 책 리스트를 확인하기 위해서는 도서관으로 가야합니다.

하지만, 도서관까지 가기엔 너무나도 번거롭죠.

그래서 생각해낸 것이 책 리스트를 복사해서 집에서도 확인할 수 있게합니다.

 

이때, 도서관에 배치된 "이달의 추천 책 리스트"이 바로 원본 데이터

집으로 가져갈 복사본캐시, 또 이라는 근접한 공간이 바로 고속 데이터 스토리지에 해당합니다.

 

캐시를 사용할 때는 몇 가지 주의사항이 있지만,

본 포스팅에서 다루기엔 무겁기 때문에 생각할 만한 요소들을 던져보겠습니다.

 

- 복사를 위해서는 복사집에 가서 500원이라는 요금을 내야합니다. 즉, 캐시를 저장할 때의 비용이 필요합니다.

- 이달의 추천 책 리스트는 변경되는 데이터입니다. 변경 전 복사본을 가지고 있으면 문제가 생깁니다.

- 종이로는 그럴일이 없겠지만, 복사된 양이 너무나도 많아 집에서 다 가지고 있기 버거울 수 있습니다.

 

이런 내용들을 계속해서 고려하며 캐시를 다뤄야 합니다.

 

 

 

📌 Cache Abstraction

🔗 Spring.io Cache Abstraction

 

스프링에서는 빈의 메소드에 캐시를 적용할 수 있는 기능을 제공합니다.

트랜잭션과 마찬가지로, AOP를 이용해 메소드 실행 과정에 투명하게 적용됩니다.

 

스프링의 캐시 추상화는 캐시 특정 기술에 종속되지 않으며 AOP를 통해 적용되어

애플리케이션 코드를 수정하지 않고 캐시 부가기능을 추가할 수 있습니다.

 

즉, 캐시 API를 코드에 추가하지 않아도 손쉽게 캐시 기능을 부여할 수 있습니다.

또한 캐시 서비스 구현 기술에 종속되지 않도록 추상화 서비스를 제공하기 때문에 환경이 바뀌거나 적용할 기술을 변경해서 캐시 서비스의 종류가 달라지더라도 애플리케이션 코드에 영향을 주지 않습니다.

더 자세히 말하면 캐싱이 필요한 비즈니스 로직에서 EhCache, redis 등 캐싱 종류에 의존하지 않고 추상화된 인터페이스로 캐싱을 적용 할 수 있습니다.

 

 

📌 Cache Manager

캐시 추상화에서는 캐시 기술을 지원하는 캐시 매니저를 빈으로 등록해야 합니다.

 

✔️ ConcurrentMapCacheManager

ConcurrentHashMap을 이용해 캐시 기능을 구현하는 간단한 캐시 매니저입니다.

캐시 정보를 Map 타입으로 메모리에 저장해두기 때문에 빠르고 별다른 설정이 필요없다는 장점이 있지만, 실제 서비스에서 사용하기에는 기능이 빈약합니다.

 

✔️ SimpleCacheManager

기본적으로 제공하는 캐시가 없습니다.

사용할 캐시를 직접 등록하여 사용하기 위한 캐시 매니저입니다.

 

✔️ EhCacheCacheManager

자바에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시 매니저입니다.

 

✔️ CaffeineCacheManager

Java 8로 Guava 캐시를 재작성한 Caffeine 캐시를 사용하는 캐시 매니저입니다.

EhCache와 함께 인기 있는 캐시 매니저인데, EhCache보다 좋은 성능을 갖는다고 해서 최근 많이 사용합니다.

 

✔️ CompositeCacheManager

한 개 이상의 캐시 매니저를 사용하도록 지원해주는 혼합 캐시 매니저입니다.

 

✔️ JCacheCacheManager

JSR-107 기반의 캐시를 사용하는 캐시 매니저입니다.

 

 

 

📌 의존성 추가

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

 

 

 

@EnableCaching

Spring에서 @Cacheable과 같은 어노테이션 기반의 캐시 기능을 사용하기 위해서는 먼저 별도의 선언이 필요합니다.

때문에, @EnableCaching 어노테이션을 설정 클래스에 추가해주어야 합니다.

 

Example

@EnableCaching
@Configuration
public class EhCacheConfiguration { /* ... */}

 

 

 

@Cacheable

적용된 메소드의 리턴값을 기준으로 캐시에 값을 저장합니다.

 

적용할 메소드에 @Cacheable 어노테이션을 붙여주면

캐시에 데이터가 없을 경우에는 기존의 로직을 실행한 후에 캐시에 데이터를 추가하고,

캐시에 데이터가 있으면 캐시의 데이터를 반환합니다.

 

cacheName(value) 값을 기준으로 캐시를 저장하는데,

만약 더 디테일한 값으로 저장해야한다면 key와 함께 저장합니다.

사용 예시를 확인해보면 이해가 쉬울거에요.

 

 

✔  Optional Element

Element Description Type
cacheName 캐시 이름 (설정 메서드 리턴값이 저장되는) String[]
value cacheName의 alias String[]
key 동적인 키 값을 사용하는 SpEL 표현식
동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값
String
condition SpEL 표현식이 참일 경우에만 캐싱 적용
- or, and 등 조건식, 논리연산 가능
String
unless 캐싱을 막기 위해 사용되는 SpEL 표현식
condition과 반대로 참일 경우에만 캐싱이 적용되지 않음
String
cacheManager 사용 할 CacheManager 지정
(EHCacheCacheManager, RedisCacheManager 등)
String
sync 여러 스레드가 동일한 키에 대한 값을 로드하려고 할 경우, 기본 메서드의 호출을 동기화
즉, 캐시 구현체가 Thread safe 하지 않는 경우, 캐시에 동기화를 걸 수 있는 속성
boolean

 

Example

@Cacheable(cacheNames = "platformTeamBooks", key = "#root.target + #root.methodName + '_'+ #p0")
public Book getPlatformTeamBook(int bookId) { /* ... */}

@Cacheable(cacheNames = "users", condition = "#user.type == 'ADMIN'")
public Book selectUser(User user) { /* ... */}

@Cacheable(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}

 

 

 

@CacheEvict

메서드가 호출 될 때 저장된 캐시를 삭제합니다.

캐싱된 데이터가 변경될 경우 (Insert, update, delete) 기존의 캐싱된 데이터가 바뀌어야 하기 때문에 지워주는 작업이 필수겠죠?

 

 

✔  Optional Element

Element Description Type
cacheName 제거할 캐시 이름 String[]
value cacheName의 Alias String[]
key 동적인 키 값을 사용하는 SpEL 표현식
동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값
String
allEntries 캐시 내의 모든 리소스를 삭제할지의 여부 boolean
condition SpEL 표현식이 참일 경우에만 삭제 진행
- or, and 등 조건식, 논리연산 가능
String
cacheManager 사용 할 CacheManager 지정
(EHCacheCacheManager, RedisCacheManager 등)
String
beforeInvocation true - 메서드 수행 이전 캐시 리소스 삭제
false - 메서드 수행 후 캐시 리소스 삭제
boolean

 

Example

@CacheEvict(key = "testKey", condition="#caching") 
public Object removeSome(boolean caching) { /* ... */}

@CacheEvict(value = "platformTeamBooks", allEntries = true)
public Book insertPlatformTeamBook(BookVO bookVO) { /* ... */}

 

 

 

@CachePut

캐시에 값을 저장하는 용도로만 사용합니다.

 

@Cacheable과 유사하게 실행 결과를 캐시에 저장하지만,

조회 시에 저장된 캐시의 내용을 사용하지는 않고 항상 메소드의 로직을 실행합니다.

 

 

✔  Optional Element

Element Description Type
cacheName 입력할 캐시 이름 String[]
value cacheNamed의 Alias String[]
key 동적인 키 값을 사용하는 SpEL 표현식
동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값
String
cacheManager 사용 할 CacheManager 지정
(EHCacheCacheManager, RedisCacheManager 등)
String
condition SpEL 표현식이 참일 경우에만 캐싱 적용
- or, and 등 조건식, 논리연산 가능
String
unless 캐싱을 막기 위해 사용되는 SpEL 표현식
condition과 반대로 참일 경우에만 캐싱이 적용되지 않음
String

 

Example

@Cacheable(key = "testKey", condition="#caching")
public Object modifySome(boolean caching) {/* ... */}

 

 

 

@Caching

하나의 메서드를 호출 할 때 Cacheable, CacheEvict 등 여러 개의 캐싱 동작을 수행해야 할 때 사용합니다.

 

 

✔  Optional Element

Element Description Type
cacheable 적용 될 @Cacheable array를 등록 Cacheable[]
evict 적용 될 @CacheEvict array를 등록 CacheEvict[]
put 적용 될 @Cacheput array를 등록 CachePut[]

 

Example

@Caching(evict = { 
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) { /* ... */ }

 

 

 

@CacheConfig

클래스 단위로 캐시 설정을 동일하게 하고 싶을 때 사용합니다.

 

 

✔  Optional Element

Element Description Type
cacheNames 해당 클래스 내 정의된 캐시 작업에서의 default 캐시 이름 String[]
cacheManager 사용 할 CacheManager 지정
(EHCacheCacheManager, RedisCacheManager 등)
String

 

Example

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {
    // ...
    @Cacheable
    public String getAddress(Customer customer) {...}
    // ...
}

 

 

 

 

Spring Cache의 설정과 관련된 내용을 다뤘습니다.

Cache를 적용하기 위해서는 위에서 잠깐 언급했던 Cache Manager가 필요합니다.

다음 포스팅은 로컬 캐시 중 성능에 유리한 Caffeine Cache를 다뤄보겠습니다.

 

오타나 잘못된 내용을 댓글로 남겨주세요!

감사합니다 ☺️