이 예제에서는 Redis를 캐시저장소로 사용합니다.
@Configuration
@EnableCaching
public class CacheConfig {
@Value("${spring.data.redis.port}")
public int port;
@Value("${spring.data.redis.host}")
public String host;
@Value("${spring.data.redis.password}")
public String password;
// 레디스 설정
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPassword(password);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
// 캐시매니저 등록
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
}
// 이름을 지정하지 않았을 때의 기본속성을 정의할 수 있다.
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(500))
.disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())
);
}
// 특정 이름의 캐시 속성을 여러개 정의할 수 있다.
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("cache1",
RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> "prefix::" + cacheName + "::")
.entryTtl(Duration.ofSeconds(3000))
.disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())
)
)
.withCacheConfiguration("cache2",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(3000))
.disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())
)
);
}
}
@Cacheable(cacheNames = "basicCache")
public List<MyEntity> findAll() {
return myRepository.findAll();
}
basicCache::SimpleKey []라는 이름으로 저장됩니다.Cacheable With key
@Cacheable(key = "'zzz'", cacheNames = "cache1")
public List<MyEntity> findByAge(Integer age) {
return myRepository.findAllByAge(age);
}
prefix::cache1::zzz라는 이름으로 저장됩니다. prefix가 붙은 이유는 Config에서 .computePrefixWith(cacheName -> "prefix::" + cacheName + "::")를 설정했기 때문입니다. @Cacheable(key = "#age", cacheNames = "cache2")
public List<MyEntity> findByAge(Integer age) {
return myRepository.findAllByAge(age);
}
#매개변수로 매개변수를 가져올 수 있습니다. 매개변수가 객체라면 #객체.변수도 가능합니다.cache2::10라는 이름으로 저장됐습니다(조회할 대 age값을 10으로 줬었다). 즉, cacheNames::key형태로 키가 저장됩니다. @CacheEvict(key = "'id'", cacheNames = "cache1")
public void update(Long id){
MyEntity myEntity = myRepository.findById(id).orElseThrow(RuntimeException::new);
myEntity.setIsUpdated(true);
}
@CacheEvict(cacheNames = "cache1", allEntries = true)
public void update(Long id){
MyEntity myEntity = myRepository.findById(id).orElseThrow(RuntimeException::new);
myEntity.setIsUpdated(true);
}
페이징 데이터는 캐싱을 도입해도 문제가 없는지를 고민해봐야 합니다. 그렇지 않으면 상황에 따라 중복이나 누락이 발생할 수도 있습니다.
- 누군가 1페이지를 요청했다. 해당 요청에 의해 1페이지 정보가 캐싱됐다.
- 100번 게시물이 갑자기 인기글이 되어 1페이지에 올라왔다. 하지만 이미 1페이지는 캐싱됐기 때문에 100번 게시물이 보이지 않는다.
- 100번 게시물이 1페이지에 올라오면서 원래 1페이지 마지막에 있던 5번 게시글이 2페이지로 밀려났다.
- 누군가 2페이지를 요청했다. 해당 요청에 의해 2페이지 정보가 캐싱됐다. 그 결과 5번 게시글은 1페이지에서도 보이고, 2페이지에서도 중복되어 보이게 된다.