- API의 호출에 따라서 캐시를 이용하여 '서버의 부하를 줄이고 API 성능을 최적화'하여 '응답시간을 단축'시키는 역할을 수행합니다.
💡Spring Boot Starter Cache
- 자주 사용되는 데이터를 ‘캐시’를 ‘메모리’에 저장하여 빠른 검색을 가능하게 하는 기능을 제공합니다. - 해당 캐시에 저장되는 데이터를 CacheManager를 통하여 메모리뿐만 아니라 디스크, 데이터베이스, 클라우드 서비스와 같은 ‘다른 저장소’에도 저장이 가능합니다.
- API에서 Database로부터 데이터를 조회하는 경우 ‘동일한 데이터를 반복하여 조회’ 해옴으로써 ‘불필요한 일을 반복하는 문제’가 발생합니다. - 이에 따라서 API 캐시를 통해서 DB로부터 반복적으로 데이터를 조회해 오는 일에 대해 최초 데이터를 조회해 온 뒤 이후는 캐시에서 데이터를 조회해 오는 처리를 수행함으로써 API의 성능을 올리며 응답시간을 단축하는 효율성을 가져옵니다.
2. Spring Boot Cache 사용처
💡 Spring Boot Cache 사용처
- 해당 경우들에서 ‘자주 조회되는 데이터’에 대해서 캐시에 저장하여 성능향상을 기대할 수 있습니다. 이를 통해 외부 저장소에 대해서 부하 및 데이터베이스 등의 부하를 줄여서 서버의 성능을 개선하는데 도움이 됩니다. - Spring Boot Cache의 사용처는 사용자의 로그인 정보(인증정보), 게시글 목록 등에 사용이 됩니다.
3) Spring Boot Cache Annotation
💡Spring Boot Cache Annotation
- Spring Boot Cache 라이브러리에서 사용되는 어노테이션 목록입니다.
1. Spring Boot Cache 어노테이션 설명
Annotation
설명
@EnableCaching
Spring Boot Cache를 사용하기 위해 '캐시 활성화'를 위한 어노테이션을 의미합니다.
@CacheConfig
캐시정보를 '클래스 단위'로 사용하고 관리하기 위한 어노테이션을 의미합니다.
@Cacheable
캐시정보를 메모리 상에 ‘저장’하거나 ‘조회’ 해오는 기능을 수행하는 어노테이션입니다.
@CachePut
캐시 정보를 메모리상에 '저장'하며 존재 시 갱신하는 기능을 수행하는 어노테이션입니다.
@CacheEvict
캐시 정보를 메모리상에 '삭제'하는 기능을 수행하는 어노테이션입니다.
@Caching
여러 개의 ‘캐시 어노테이션’을 ‘함께 사용’할 때 사용하는 어노테이션입니다.
2. 주요 Annotation 비교하며 이해하기
💡 Spring Boot Cache에서는 아래의 주요한 어노테이션을 사용합니다.
어노테이션
주요 기능
캐싱 실행 시점
@Cacheable
캐시 조회, 저장 기능
- 캐시 존재 시 ‘메서드 호출 전 실행’ - 캐시 미 존재 시 ‘메서드 호출 후 실행’
@CachePut
캐시 저장 기능
- 캐시 존재 시 ‘메서드 호출 후 실행’ - 캐시 미 존재 시 ‘메서드 호출 후 실행’
@CacheEvict
캐시 삭제 기능
- beforeInvocation 속성값이 true일때 ‘메서드 호출 전 실행’ - beforeInvocation 속성값이 false일때 ‘메서드 호출 후 실행’
[ 더 알아보기 ]
💡 메서드의 호출 전/후 실행은 무슨 차이일까?
- 메서드의 호출 전에 실행 시 DB로부터 데이터를 처리하는 부분에 대해서 줄어들게 됩니다. - 반대로 메서드의 호출 후에 실행하는 경우 DB로부터 데이터를 처리하여 가져오는 부분에 대해서 처리하는 부분이 늘어납니다.
💡 @CacheEvict의 beforeInvocation 속성이란?
- 캐시의 데이터를 삭제하는데 메서드 호출 전/후에 발생할지에 대해 설정하는 속성을 의미합니다. - 속성의 기본값은 false이며 메서드 실행 전에 캐시에서 데이터를 삭제하여 예외가 발생한다면 캐시에서 데이터가 삭제되지 않습니다. - 이에 따라 예외가 발생할 가능성이 있는 메서드에 대해서 beforeInvocation= “true”를 사용하지 않는 것이 좋습니다.
💡 @Cacheable과 @CachePut의 다른 점은?
- @Cacheable의 경우는 ‘캐시가 존재하지 않을 경우’ 캐시를 저장하지만 @CachePut의 경우는 ‘캐시의 존재 여부를 떠나서’ 항상 저장 혹은 갱신을 수행합니다.
1. @EnableCaching
💡 @EnableCaching
- Spring Boot Cache를 사용하기 위해 '캐시 활성화'를 위해 사용됩니다. - 해당 어노테이션의 선언 위치는 CacheManager()를 구현한 @Configuration부분에서 선언하여 사용합니다.
💡 @EnableCaching 속성 값
속성
속성 기본 값
설명
value
빈 이름
캐시 매니저 이름을 지정한다.
order
Ordered.LOWEST_PRECEDENCE
캐시 관련 빈들의 우선순위를 지정한다.
keyGenerator
-
캐시에서 사용할 키 생성기를 지정한다.
cacheManager
-
사용할 캐시 매니저를 직접 지정한다.
cacheResolver
-
사용할 캐시 리졸버를 직접 지정한다.
proxyTargetClass
false
프록시를 생성할 때 CGLIB를 사용할지, 아니면 JDK Dynamic Proxy를 사용할지 결정한다.
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
log.debug("[+] CacheConfig Start !!! ");
// "codeCacheInfo"라는 캐시를 관리합니다.
return new ConcurrentMapCacheManager("codeCache");
}
}
2. @CacheConfig
💡 @CacheConfig
- 캐시를 '클래스 단위'로 '사용하고 관리'하기 위해 사용됩니다.
- 이를 사용하면 ‘클래스 단위로 관리’를 하면 동일한 이름의 캐시를 반복하여 입력할 필요를 줄여줍니다. - 해당 어노테이션의 선언 위치는 캐시를 사용하는 @Service 부분에서 선언하여 사용합니다.
💡@EnableCaching 속성
속성
설명
cacheNames
캐시할 데이터의 이름을 지정합니다.
cacheManager
캐시 매니저의 이름을 지정합니다.
cacheResolver
캐시 리졸버의 이름을 지정합니다.
keyGenerator
캐시 키 생성기의 이름을 지정합니다.
cacheManagerCustomizers
캐시 매니저 커스터마이저의 이름을 지정합니다.
key
캐시할 데이터에서 사용할 키를 지정합니다.
condition
캐시를 적용할 조건을 지정합니다.
unless
캐시 적용을 제외할 조건을 지정합니다.
sync
캐시 동기화 여부를 지정합니다.
beforeInvocation
메서드 실행 전에 캐시를 적용할지 여부를 지정합니다.
💡 예시 설명
- 해당 CodeServiceImpl라는 클래스는 CodeService의 인터페이스의 구현체로 사용되는 파일입니다.
1. @CacheConfig(cacheNames = "code")라는 속성으로 클래스 내부에서 “code”라는 값으로 공통적으로 사용하도록 구성하였습니다.
@Service
@CacheConfig(cacheNames = "code")
public class CodeServiceImpl implements CodeService {
...
}
3. @Cacheable
💡 @Cacheable
- 캐시 정보를 메모리 상에 '저장'하거나 '조회' 해오는 기능을 수행할 때 사용됩니다.
- 상세한 설명으로는 캐시의 속성값인 '값(value)과 키(key)'를 기반으로 수행이 됩니다. - 최초 호출이 된 경우에는 메서드가 호출되어 '캐시 정보를 저장' 하며, 동일하게 재 호출이 된 경우에는 메서드의 호출 없이 '저장된 캐시의 정보를 조회하여 반환'합니다. - @CachePut 어노테이션과 비슷한 기능을 수행하지만 '캐시 정보를 조회'하는데 주로 사용합니다. - 해당 어노테이션의 선언 위치는 주로 @Service를 선언한 인터페이스의 구현체 부분에서 함께 선언하여 사용합니다.
💡 @Cacheable 속성
속성
설명
value
캐시로 사용될 이름을 지정합니다.
key
캐시의 key로 사용될 SpEL 표현식을 이용하여 지정합니다.
condition
캐시를 저장할 조건을 지정하는 SpEL 표현식을 이용하여 지정합니다.
unless
캐시를 저장하지 않을 조건을 지정하는 SpEL 표현식을 이용하여 지정합니다.
cacheManager
사용할 캐시 매니저를 지정하는데 사용될 캐시 매니저의 이름을 지정합니다.
cacheResolver
사용할 캐시 해결자 빈을 지정하는데 사용될 캐시 해결자 빈 이름을 지정합니다.
💡 예시 설명
- 해당 selectCodeList()의 메서드는 CodeServiceImpl라는 클래스는 CodeService의 인터페이스의 구현체로 사용되는 클래스 파일에 포함된 함수입니다.
1. @Cacheable(value = "codeCacheInfo", key = "#codeDto.grpCd")를 통해서 해당 메서드의 호출을 하는 경우 codeCacheInfo의 캐시의 값에 객체로 전달받은 codeDto.grpCd 값에 따라서 해당 캐시가 저장이 됩니다. 2. 동일한 값과 키값을 통해서 메서드의 호출이 발생하는 경우 메서드를 호출하지 않고 캐시에서 데이터를 가져와서 결과값을 반환해 줍니다.
@Service
public class CodeServiceImpl implements CodeService {
/**
* 코드 리스트를 조회합니다.
*
* @param codeDto CodeDto
* @return List<CodeDto>
*/
@Transactional(readOnly = true)
@Cacheable(value = "codeCacheInfo", key = "#codeDto.grpCd")
public List<CodeDto> selectCodeList(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
return cm.selectCodeList(codeDto);
}
}
4. @CachePut
💡 @CachePut
- 캐시 정보를 메모리상에 '저장'하며 존재 시 갱신하는 기능을 수행할 때 사용합니다.
- 상세한 설명으로는 캐시의 속성값인 '값(value)과 키(key)'를 기반으로 수행이 됩니다. - 최초 호출이 된 경우에는 메서드가 호출되어 ‘캐시 정보를 저장’하며, 동일하게 재 호출이 된 경우에도 메서드가 호출되어 ‘캐시 정보를 갱신(수정 및 저장)’하여 캐시의 정보를 반환합니다. - @Cacheable 어노테이션과 비슷한 기능을 수행하지만 '캐시 정보를 저장'하는데 주로 사용이 됩니다. - 해당 어노테이션의 선언 위치는 주로 @Service를 선언한 인터페이스의 구현체 부분에서 함께 선언하여 사용합니다.
💡 @CachePut 속성
속성
설명
value
캐시 이름을 지정합니다.
key
캐시에 저장할 때 사용할 키를 지정한다. SpEL 표현식으로 작성 가능합니다
condition
캐시에 저장할 조건을 지정한다. SpEL 표현식으로 작성 가능하며, 조건이 false 일 경우 캐시 저장을 하지 않습니다
unless
캐시에 저장하지 않을 조건을 지정한다. condition과 반대로 동작하며, SpEL 표현식으로 작성 가능합니다
keyGenerator
캐시에 사용할 키 생성기를 지정합니다
cacheManager
캐시 매니저를 지정합니다.
💡 예시 설명
- 해당 selectCodeByCd()의 메서드는 CodeServiceImpl라는 클래스는 CodeService의 인터페이스의 구현체로 사용되는 클래스 파일에 포함된 함수입니다.
1. @CachePut(value = "codeCacheInfo", key = "#cd")를 통해서 해당 메서드의 호출을 하는 경우 codeCacheInfo의 캐시의 값에 파라미터로 전달받은 cd 값에 따라서 해당 캐시가 저장이 됩니다. 2. 동일한 값과 키를 통해서 메서드의 호출이 발생하더라도 메서드는 호출되어 해당 캐시를 갱신하여 결과값을 반환해 줍니다.
@Service
public class CodeServiceImpl implements CodeService {
/**
* 코드 키 값을 기반으로 코드 정보를 조회합니다
*
* @param cd String
* @return CodeDto
*/
@Transactional(readOnly = true)
@CachePut(value = "codeCacheInfo", key = "#cd")
public CodeDto selectCodeByCd(String cd) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
return cm.selectCodeByCd(cd);
}
}
5. @CacheEvict
💡 @CacheEvict
- 캐시 정보를 메모리상에 '삭제'하는 기능을 수행할 때 사용합니다.
- 상세한 설명으로는 캐시는 속성값인 '값(value)과 키(key)'를 기반으로 수행이 됩니다. - 어노테이션의 속성인 beforeInvocation의 값이 true이면 '메서드 호출 전'에 실행이 되며, false이면 '메서의 호출 후에 실행'이 됩니다.
- 해당 어노테이션의 선언 위치는 주로 @Service를 선언한 인터페이스의 구현체 부분에서 함께 선언하여 사용합니다.
💡 @CacheEvict 속성 값
속성
설명
value
캐시 이름을 지정합니다
key
캐시에서 삭제할 키 값을 지정합니다.
condition
캐시 삭제 조건. SpEL 표현식 사용 가능합니다
allEntries
전체 캐시 삭제 여부입니다
beforeInvocation
메소드 실행 전 캐시 삭제 여부입니다
[ 예시 설명 ]
- 해당 deleteCode()의 메서드는 CodeServiceImpl라는 클래스는 CodeService의 인터페이스의 구현체로 사용되는 클래스 파일에 포함된 함수입니다.
1. @CacheEvict(value = "codeCacheInfo", key = "#codeDto.grpCd", beforeInvocation = false)를 통해서 해당 메서드의 호출을 하는 경우 codeCacheInfo의 캐시 값과 codeDto.grpCd의 키 값에 따라서 캐시를 삭제합니다. 2. beforeInvocation 속성을 false로 지정하였기에 메서드가 실행된 이후에 캐시를 삭제하도록 처리하였습니다.
@Service
public class CodeServiceImpl implements CodeService {
/**
* 코드 삭제
*
* @param codeDto CodeDto
* @return int
*/
@Transactional
@CacheEvict(value = "codeCacheInfo", key = "#codeDto.grpCd", beforeInvocation = false)
public int deleteCode(CodeDto codeDto) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
try {
return cm.deleteCode(codeDto);
} catch (Exception e) {
throw new BusinessExceptionHandler(e.getMessage(), ErrorCode.INSERT_ERROR);
}
}
}
6. @Caching
💡 @Caching이란?
- 여러 개의 '캐시 어노테이션을 함께 사용할 때 사용'됩니다.
- 예를 들어, @CachePut과 @CacheEvict를 함께 사용할 때 사용됩니다.
[참고] @Caching의 속성 값
속성
설명
cacheable
캐시 정보를 조회하는데 사용하는 @Cacheable 어노테이션을 단일 혹은 다중으로 사용할 수 있도록 하는 속성입니다.
put
캐시 정보를 저장하는데 사용하는 @CachePut 어노테이션을 단일 혹은 다중으로 사용할 수 있도록 하는 속성입니다.
evict
캐시 정보를 삭제하는데 사용하는 @CacheEvict 어노테이션을 단일 혹은 다중으로 사용할 수 있도록 하는 속성입니다.
[ 예시 설명 ]
- 해당 selectCodeList()의 메서드는 CodeServiceImpl라는 클래스는 CodeService의 인터페이스의 구현체로 사용되는 클래스 파일에 포함된 함수입니다. - @Caching 어노테이션을 이용하여 조회를 수행하는 cacheable 속성과 수정을 수행하는 put 속성을 함께 사용하도록 구성하였습니다. - 메서드를 호출하는 경우 조회와 생성이 함께 수행이 되도록 구성하였습니다.
- 캐시를 관리하고 캐시 된 ‘데이터를 저장’하고 ‘반환’하는 역할을 합니다. - 일반적으로 메모리 상에 저장되는 데이터를 디스크, 데이터베이스, 클라우드 서비스와 같은 저장소에 저장이 가능하도록 합니다. - 해당 캐시는 Redis, Ehcache, Caffeine 등 다양한 캐시 라이브러리와 연동하여 사용할 수 있습니다.
💡 CacheManager의 종류
CacheManager
캐시 저장 위치
설명
ConcurrentMapCacheManager
메모리 상에 캐시 저장
ConcurrentHashMap 기반 캐시 구현체를 의미합니다.
CaffeineCacheManager
메모리 상에 캐시 저장
Caffeine 기반 캐시 구현체
SimpleCacheManager
메모리 상에 캐시 저장
단순 캐시 구현체를 의미합니다.
EhCacheCacheManager
디스크 또는 데이터베이스에 저장
Ehcache 기반 캐시 구현체를 의미합니다.
HazelcastCacheManager
메모리 상에 캐시 저장
Hazelcast 기반 캐시 구현체를 의미합니다.
InfinispanCacheManager
메모리 상에 캐시 저장
Infinispan 기반 캐시 구현체를 의미합니다.
RedisCacheManager
Redis 내의 데이터베이스에 저장
Redis 기반 캐시 구현체를 의미합니다.
CouchbaseCacheManager
Couchbase 내의 데이터베이스에 저장
Couchbase 기반 캐시 구현체를 의미합니다.
GemfireCacheManager
자체 저장소에 저장
GemFire 기반 캐시 구현체를 의미합니다.
CoherenceCacheManager
자체 저장소에 저장
Oracle Coherence 기반 캐시 구현체를 의미합니다.
[ 더 알아보기 ] 💡 로컬 캐시(Local Cache)
- 캐시를 사용하는 한 개의 서버에서만 유효한 캐시입니다. 이는 서버의 메모리에 저장되며 서버가 재시작되거나 캐시가 만료될 때까지 유지됩니다.
💡 글로벌 캐시 (Globacl Cache)
- 여러 서버에서 공유하는 캐시입니다. 이를 위해서는 분산 캐시 기술이 필요하며 대표적인 기술로는 Redis, Memcached, Hazelcast 등이 있습니다. - 글로벌 캐시를 사용하면 여러 서버에서 공유하는 캐시를 이용하여 성능을 향상할 수 있습니다.
💡 해당 예시에서는 @EnableCaching을 통해서 Spring Boot Cache를 사용함을 선언하였고 CacheManager인 ConcurrentMapCacheManager를 통해서 캐시를 관리합니다.
💡@EnableCaching 란?
- 캐시를 사용하기 위해 필요한 어노테이션으로 Spring Boot 애플리케이션에서 캐싱을 활성화하는 데 사용되며 @Configuration이 포함된 클래스에서 이를 적용합니다.
package com.adjh.multiflexapi.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 'ConcurrentMapCacheManager'를 통해서 캐시를 관리합니다.
*
* @author : lee
* @fileName : CacheConfig
* @since : 2023/03/28
*/
// 해당 Annotation을 추가합니다.
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("codeCacheInfo");
}
}
💡 CacheManager의 등록된 캐시가 아닐 경우 처리과정
- 아래와 같이 CacheManager에는 “codeCacheInfo”라는 캐시만 관리하게 저장한 상태고 code 캐시를 사용함을 지정하였습니다
/**
* 코드 키 값을 기반으로 코드 정보를 조회합니다
*
* @param cd String
* @return CodeDto
*/
@Cacheable(value = "code", key = "#cd")
public CodeDto selectCodeByCd(String cd) {
CodeMapper cm = sqlSession.getMapper(CodeMapper.class);
return cm.selectCodeByCd(cd);
}