캐싱은 Spring 내부에서도 비교적 간단하게 구성할 수 있다.
redis 와 함께 SpringBoot 에서 캐싱을 적용하는 방법을 알아보자.
캐싱을 사용하기 위한 configuration 을 작성해준다
// 나머지 import 문들
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@Configuration
@EnableCaching // 캐싱 사용
public class CacheConfig {
@Bean
// CacheManager로 진행해도 정상 동작
public RedisCacheManager cacheManager(
RedisConnectionFactory redisConnectionFactory
) {
// 설정 구성을 먼저 진행한다.
// Redis를 이용해서 Spring Cache를 사용할 때
// Redis 관련 설정을 모아두는 클래스
RedisCacheConfiguration configuration = RedisCacheConfiguration
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()
// 기본 캐시 유지 시간 (Time To Live)
.entryTtl(Duration.ofSeconds(10))
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
return RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(configuration)
.build();
}
}
@EnableCaching
: CacheManger의 구현체가 Bean으로 등록되어야 한다.RedisCacheManager
를 Bean 으로 등록하자.스프링에서는 어노테이션을 통해 간단하게 캐싱을 적용할 수 있다.
// cacheNames: 메서드로 인해서 만들어질 캐시를 지칭하는 이름
// key: 캐시에서 데이터를 구분하기 위해 활용할 값
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
log.info("Read One: {}", id);
return repository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(()
-> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
cacheNames
: 캐시를 구분하기 위해 붙이는 이름. 다른 메소드에서 해당 캐시를 다루려면 이 이름을 적어야 함.key
: 캐싱을 위한 db 에서 데이터를 구분하기 위한 값.SpEL
를 사용하는데 위에서는 메소드 첫번째 인자(args[0]) 을 지정한다.- List Data 를 반환하는 메소드를 저장하기 위해서나 여러 key 를 적용하기 위해 추가적인 사항은 아래 문서를 통해 확인하자.
Spring - Declarative annotation-based caching
@CachePut(cacheNames = "itemCache", key = "#result.id")
public ItemDto create(ItemDto dto) {
return ItemDto.fromEntity(itemRepository.save(Item.builder()
.name(dto.getName())
.description(dto.getDescription())
.price(dto.getPrice())
.stock(dto.getStock())
.build()
));
}
@Cacheable
은 데이터를 캐시에서 발견할 경우(Hit), 메서드 자체를 실행하지 않는다. 반면, @CachePut
은 항상 메서드를 실행하고, 결과를 캐싱한다.여기서 주목할 만한건,
create()
의 경우key
를#result.id
로 설정한다는 점이다.
이는 반환되는ItemDto.id
를 활용한다는 의미인데, 이렇게 만들어진 데이터가 캐시에 저장되기 때문에 위에서 작성한readOne
메서드도 그 결과를 활용한다.
이는 Redis에 저장하는 데이터의 Key가cacheName::key
의 형태로 저장되기 때문에,readOne
이 찾는cacheName::key
도 동일한 Key를 찾아내서, Redis의 데이터를 활용할 수 있기 때문이다.
@CachePut(cacheNames = "itemCache", key = "args[0]")
@CacheEvict(cacheNames = "itemAllCache", allEntries = true)
public ItemDto update(Long id, ItemDto dto) {
Item item = itemRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
item.update(dto);
return ItemDto.fromEntity(itemRepository.save(item));
}
Evict라는 말에서 유추 가능하듯,
@CacheEvict
는 주어진 정보를 바탕으로 저장된 캐시를 지워준다. key를 통해서 명확히 하나의 캐시를 지정할수도 있지만, 지금은 모든 아이템을 저장한 캐시인 itemAllCache를 목표로 한다.
아이템의 정보가 바뀌었으니, 데이터를 전부 돌려준 결과가 더이상 유효하지 않다고 이야기 하는거라 생각할 수 있다.