시작 프로젝트
CacheConfig.java
package com.example.redis.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
// 설정 구성을 먼저 진행한다.
// Redis를 이용해서 Spring Cache를 사용할 때
// Redis 관련 설정을 모아두는 클래스
RedisCacheConfiguration configuration = RedisCacheConfiguration
// 대부분의 기본설정을 사용할 거임
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()// 널은 캐싱 안함
// 기본 캐시 유지 시간 (Time To Live[Ttl])
.entryTtl(Duration.ofSeconds(10))// 10초간 캐시 데이터 유지
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
return RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(configuration)
.build();
}
}
레디스를 캐시로 쓰기 위해서 필요한 기본적인 설정이다.
캐싱을 동작을 할때 따로 설정을 바꾸어 저장을 하지 않으면 이 설정을 바탕으로 캐싱을 저장, 관리를 한다.
@EnableCaching을 적용했다면, 이제 어노테이션을 바탕으로 메서드의 결과를 캐싱할 수 있습니다. 대표적인 어노테이션으로 @Cacheable, @CachePut, @CacheEvict가 있습니다.
Cache-Aside 전략
ItemService.java
readOne()
// 이 메서드의 결과는 캐싱이 가능하다.
// cacheNames: 요 메서드로 인해서 만들어질 캐시를 지침하는 이름
// key: arge[0]의 의미는 메소드의 0번째 매개변수를 인자로 사용할 거다 여기서는 Long id에 들어오는 값이 키가 됨 캐시 데이터를 구분하기 위해 활용 되는 값
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
return itemRepository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(() ->
new ResponseStatusException(HttpStatus.NOT_FOUND));
}
readAll()
@Cacheable(cacheNames = "itemAllCache", key = "methodName")
public List<ItemDto> readAll() {
return itemRepository.findAll()
.stream()
.map(ItemDto::fromEntity)
.toList();
}
cacheNames는 Spring 내부에서 캐시를 구분하기 위해 붙여주는 이름입니다. 나중에 캐시된 데이터를 삭제하고 싶다면, 이 이름을 기억해야 합니다.
key는 Redis, 또는 다른 캐싱을 위한 데이터베이스에서 데이터를 구분하기 위해 사용할 값을 지정합니다. 여기선 SpEL(Spring Expression Language)를 사용하는데, 지금은 현재 메서드의 첫번째 인자(.args[0]) id를 지정하고 있습니다.
실행해서 요청하고 10초안에 다시 요청을 하면 데이터를 DB에서 찾는게 아니라 redis에서 찾는걸 볼 수 있다.
설명을 하자면 데이터가 레디스에 없다면 DB에서 찾아서 레디스에 저장하고 설정한 시간(여기서는 기본 설정인 10초)가 지나면 사라지고 다시 요을 하면 다시 레디스에 저장된다.
레디스에 저장되어있는 10초 동안은 그냥 readOne메소드를 실행 하지 않는다.
Write-Through 전략
//result.id 결과의 아이디 ItemDto의 id필드
@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())
.build()));
}
readOne 메소드에서 쓴 cachNames의 값고 같은걸 쓰고 있다.
그럼 저장하고 10초안에 추가된데이터를 읽으면 redis의 데이터를 읽어 들인다.
그럼 readOne메소드는 실행 되지 않는다.
cacheNames의 값이 readOne의 cacheNames의 값과 다르면 읽을 수 없다.
데이터를 업테이트를 해야한다면 readAll()의 결과가 레디스에 남아 있다면 이 데이터도 수정이 되거나 지워져야 한다.
그럴 때 쓰는 어노테이션이다.
update()
@CachePut(cacheNames = "itemCache", key = "args[0]")
@CacheEvict(cacheNames = "itemAllCache", allEntries = true)// itemAllCache로 시작하는 모든데이터를 지우겠다는 의미
public ItemDto update(Long id, ItemDto dto) {
Item item = itemRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
item.setName(dto.getName());
item.setDescription(dto.getDescription());
item.setPrice(dto.getPrice());
return ItemDto.fromEntity(itemRepository.save(item));
}
업데이트 요청을 하면 readAll()로인해 생긴 itemAllCache가 사라진것을 볼 수 있다.
데이터를 삭제를해야하는데 그데이터가 레디스에 있으면 안됩니다.
위에서는 데이터가 itemAllCache itemCache에 있을 겁니다. 그럼 둘의 데이터를 삭제해야합니다.
@Caching(evict = {
@CacheEvict(cacheNames = "itemAllCache", allEntries = true),
@CacheEvict(cacheNames = "itemCache", key = "#id")
})
public void delete(Long id) {
itemRepository.deleteById(id);
}
public Page<ItemDto> search(
@RequestParam(name = "q")
String query,
Pageable pageable
) {
return itemService.searchByName(query, pageable);
}
@Cacheable(
cacheNames = "itemSearchCache",
key = "{ args[0], args[1].pageNumber, args[1].pageSize }"
)
public Page<ItemDto> searchByName(String query, Pageable pageable) {
return itemRepository.findAllByNameContains(query, pageable)
.map(ItemDto::fromEntity);
}
public interface ItemRepository extends JpaRepository<Item, Long> {
Page<Item> findAllByNameContains(String name, Pageable pageable);
}
.withInitialCacheConfigurations()을 사용하면 됨
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
// 설정 구성을 먼저 진행한다.
// Redis를 이용해서 Spring Cache를 사용할 때
// Redis 관련 설정을 모아두는 클래스
RedisCacheConfiguration configuration = RedisCacheConfiguration
// 대부분의 기본설정을 사용할 거임
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()// 널은 캐싱 안함
// 기본 캐시 유지 시간 (Time To Live[Ttl])
.entryTtl(Duration.ofSeconds(120))// 10초간 캐시 데이터 유지
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
return RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(configuration)
//.withInitialCacheConfigurations() 여기에 추가
.build();
}
withInitialCacheConfigurations()를 살펴보면
public RedisCacheManagerBuilder withInitialCacheConfigurations(Map<String, RedisCacheConfiguration> cacheConfigurations) {
((Map)RedisAssertions.requireNonNull(cacheConfigurations, "CacheConfigurations must not be null", new Object[0])).forEach((cacheName, cacheConfiguration) -> {
RedisAssertions.requireNonNull(cacheConfiguration, "RedisCacheConfiguration for cache [%s] must not be null", new Object[]{cacheName});
});
this.initialCaches.putAll(cacheConfigurations);
return this;
}
Map<String, RedisCacheConfiguration> cacheConfigurations
String은 @Cacheable(cacheNames = "itemCache", key = "args[0]")에서 cacheNames으로 전달 했던 키이름이고 RedisCacheConfiguration는 설정을 넣어야한다.
예시를 보면
package com.example.redis.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@Configuration
@EnableCaching
public class CacheConfig {
// @Bean
// public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
// // 설정 구성을 먼저 진행한다.
// // Redis를 이용해서 Spring Cache를 사용할 때
// // Redis 관련 설정을 모아두는 클래스
// RedisCacheConfiguration configuration = RedisCacheConfiguration
// // 대부분의 기본설정을 사용할 거임
// .defaultCacheConfig()
// // null을 캐싱 할것인지
// .disableCachingNullValues()// 널은 캐싱 안함
// // 기본 캐시 유지 시간 (Time To Live[Ttl])
// .entryTtl(Duration.ofSeconds(120))// 10초간 캐시 데이터 유지
// // 캐시를 구분하는 접두사 설정
// .computePrefixWith(CacheKeyPrefix.simple())
// // 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
// .serializeValuesWith(
// SerializationPair.fromSerializer(RedisSerializer.java())
// );
//
// return RedisCacheManager
// .builder(redisConnectionFactory)
// .cacheDefaults(configuration)
// .build();
// }
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
return RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfiguration())
.withInitialCacheConfigurations(customConfigurationMap())
.build();
}
private Map<String, RedisCacheConfiguration> customConfigurationMap() {
Map<String, RedisCacheConfiguration> customConfigurationMap = new HashMap<>();
customConfigurationMap.put(RedisCacheKey.itemCache, itemCacheConfiguration());
customConfigurationMap.put(RedisCacheKey.itemAllCache, itemAllCacheConfiguration());
customConfigurationMap.put(RedisCacheKey.itemSearchCache, itemSearchCacheConfiguration());
return customConfigurationMap;
}
private RedisCacheConfiguration defaultCacheConfiguration() {
return RedisCacheConfiguration
// 대부분의 기본설정을 사용할 거임
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()// 널은 캐싱 안함
// 기본 캐시 유지 시간 (Time To Live[Ttl])
.entryTtl(Duration.ofSeconds(120))// 10초간 캐시 데이터 유지
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
}
//itemCache의 설정
private RedisCacheConfiguration itemCacheConfiguration() {
return RedisCacheConfiguration
// 대부분의 기본설정을 사용할 거임
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()// 널은 캐싱 안함
// 기본 캐시 유지 시간 (Time To Live[Ttl])
.entryTtl(Duration.ofSeconds(120))// 10초간 캐시 데이터 유지
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
}
//itemAllCache의 설정
private RedisCacheConfiguration itemAllCacheConfiguration() {
return RedisCacheConfiguration
// 대부분의 기본설정을 사용할 거임
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()// 널은 캐싱 안함
// 기본 캐시 유지 시간 (Time To Live[Ttl])
.entryTtl(Duration.ofSeconds(120))// 10초간 캐시 데이터 유지
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
}
//itemSearchCache의 설정
private RedisCacheConfiguration itemSearchCacheConfiguration() {
return RedisCacheConfiguration
// 대부분의 기본설정을 사용할 거임
.defaultCacheConfig()
// null을 캐싱 할것인지
.disableCachingNullValues()// 널은 캐싱 안함
// 기본 캐시 유지 시간 (Time To Live[Ttl])
.entryTtl(Duration.ofSeconds(120))// 10초간 캐시 데이터 유지
// 캐시를 구분하는 접두사 설정
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
}
}
이렇게 설정을 여러 넣을 수 있다.