๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring Boot/๊ฐœ๋ฐœ ๊ธฐ๋ก

Spring Cacheable์˜ ๋™์ž‘ ์›๋ฆฌ

by oliviarla 2024. 9. 22.

Cacheable ์ด๋ž€

Spring์—์„œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์บ์‹ฑ ์ „๋žต์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

๊ฐ ์บ์‹œ ๋ฒค๋”์‚ฌ์—์„œ ์ œ๊ณตํ•˜๋Š” CacheManager๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  ์›ํ•˜๋Š” ๋ฉ”์„œ๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ๋ฉด ๋‹ค์–‘ํ•œ ์บ์‹œ ์ €์žฅ์†Œ๋ฅผ ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

Redis๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•œ๋‹ค. ๋จผ์ €, Spring Data Redis์— ๋Œ€ํ•œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด Configuration์— Redis์™€ ์—ฐ๊ฒฐ์„ ์œ„ํ•œ RedisConnectionFactory์™€ RedisCacheManager์˜ ๋นˆ์„ ๋“ฑ๋กํ•œ๋‹ค.

@Configuration
@EnableCaching
public class CacheConfig {
  @Value("${spring.data.redis.host}")
  private String host;

  @Value("${spring.data.redis.port}")
  private int port;
  
  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port),
        LettuceClientConfiguration.builder()
            .commandTimeout(Duration.ofSeconds(10))
            .shutdownTimeout(Duration.ofSeconds(30))
            .build());
  }
  
  @Bean
  public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {

    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
        .serializeKeysWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new StringRedisSerializer()))
        .serializeValuesWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new JdkSerializationRedisSerializer()));

    return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory)
        .withCacheConfiguration("postByAuthor",
            redisCacheConfiguration.entryTtl(Duration.ofSeconds((long) 60 * 60 * 24)))
        .build();
  }
}

 

์บ์‹ฑ์„ ์ ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฉ”์„œ๋“œ์— @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด ์บ์‹œ ์ €์žฅ์†Œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฐ€์ ธ์™€ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋งŒ์•ฝ ์บ์‹œ ์ €์žฅ์†Œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค๋ฉด ๋ฉ”์„œ๋“œ ์›๋ณธ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ ํ›„ ๊ฒฐ๊ณผ๊ฐ’์„ ์บ์‹œ์— ์ €์žฅํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

  @Cacheable(cacheManager = "redisCacheManager", cacheNames = "postByAuthor")
  public List<Post> getPostsByAuthor(Long authorId) {
    List<Post> list = postJpaRepository.findAllByAuthorId(authorId);
    return list.isEmpty() ? null : list;
  }

์ œ๊ณตํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜

์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ œ๊ณตํ•œ๋‹ค.

@EnableCaching

  • Spring Cache์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์„ค์ • ํด๋ž˜์Šค์— ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ์•„์ฃผ์–ด์•ผ ํ•œ๋‹ค.
  • ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋‹ฌ ๋•Œ CacheManager ๋นˆ์„ ํ•จ๊ป˜ ๋“ฑ๋กํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
    • ์™ธ๋ถ€ ์บ์‹œ ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์บ์‹œ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋“ฑ์˜ ์ถ”๊ฐ€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์„ ์ง„ํ–‰ํ•  ๊ฒฝ์šฐ ์„ค์ • ํด๋ž˜์Šค์— cacheManager ๋นˆ์„ ๋“ฑ๋กํ•ด์•ผ ํ•œ๋‹ค.
    • ๋”ฐ๋กœ cacheManager๋ฅผ ๋นˆ์„ ๋“ฑ๋กํ•˜์ง€ ์•Š์œผ๋ฉด ConcurrentHashMap ๊ธฐ๋ฐ˜ ๋กœ์ปฌ ์บ์‹œ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

@Cacheable

  • ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ์บ์‹œ ์•„์ดํ…œ์— ์ €์žฅํ•œ๋‹ค.
  • ์บ์‹œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜๋ฉด, ์บ์‹œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋งŒ์•ฝ ์บ์‹œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด, ๋ฉ”์„œ๋“œ ๋กœ์ง ์ˆ˜ํ–‰ ํ›„ ๋ฐ˜ํ™˜๊ฐ’์„ ์บ์‹œ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

๐Ÿšจ ์ฃผ์˜

  • key, keyGenerator ๋Š” ํ•จ๊ป˜ ์“ฐ์ผ ์ˆ˜ ์—†๋‹ค.
  • cacheManager, cacheResolver๋Š” ํ•จ๊ป˜ ์“ฐ์ผ ์ˆ˜ ์—†๋‹ค.

Attribute Description

cacheNames ์บ์‹œ ์ด๋ฆ„
value cacheNames์˜ Alias
key SpEL ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•ด ์บ์‹œ ํ‚ค๋ฅผ ๋™์  ์ƒ์„ฑํ•˜๋„๋ก ํ•œ๋‹ค.
keyGenerator ์‚ฌ์šฉํ•  KeyGenerator๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
condition SpEL ํ‘œํ˜„์‹์œผ๋กœ ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์—ฌ true์ผ ๊ฒฝ์šฐ์—๋งŒ ์บ์‹ฑ์ด ์ ์šฉ๋˜๋„๋ก ํ•œ๋‹ค.
unless SpEL ํ‘œํ˜„์‹์œผ๋กœ ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์—ฌ true์ผ ๊ฒฝ์šฐ์— ์บ์‹ฑ์ด ์ ์šฉ๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
cacheManager ์‚ฌ์šฉํ•  CacheManager๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
cacheResolver ์‚ฌ์šฉํ•  CacheResolver๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
sync ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์ผํ•œ ํ‚ค์— ๋Œ€ํ•œ ๊ฐ’์„ ๋กœ๋“œํ•˜๋ ค๊ณ  ํ•  ๊ฒฝ์šฐ, ๊ธฐ๋ณธ ๋ฉ”์„œ๋“œ์˜ ํ˜ธ์ถœ์„ ๋™๊ธฐํ™”

@CacheEvict

  • ์˜ค๋ž˜๋˜๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•  ๋•Œ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์บ์‹œ ์•„์ดํ…œ์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด ํ™œ์šฉํ•œ๋‹ค.
  • ๋ฉ”์„œ๋“œ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ ๋ฐ˜ํ™˜ ๊ฐ’์ด ๋ฌด์‹œ๋˜์–ด void ๋ฉ”์„œ๋“œ์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Attribute Description

cacheNames ์บ์‹œ ์ด๋ฆ„
value cacheNames์˜ Alias
key SpEL ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•ด ์บ์‹œ ํ‚ค๋ฅผ ๋™์  ์ƒ์„ฑํ•˜๋„๋ก ํ•œ๋‹ค.
condition SpEL ํ‘œํ˜„์‹์œผ๋กœ ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์—ฌ true์ผ ๊ฒฝ์šฐ์—๋งŒ ์บ์‹ฑ์ด ์ ์šฉ๋˜๋„๋ก ํ•œ๋‹ค.
unless SpEL ํ‘œํ˜„์‹์œผ๋กœ ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์—ฌ true์ผ ๊ฒฝ์šฐ์— ์บ์‹ฑ์ด ์ ์šฉ๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
cacheManager ์‚ฌ์šฉํ•  CacheManager๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
cacheResolver ์‚ฌ์šฉํ•  CacheResolver๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
allEntries ์บ์‹œ์— ์ €์žฅ๋œ ๊ฐ’์„ ๋ชจ๋‘ ์ œ๊ฑฐํ• ์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€
beforeInvocation ๋ฉ”์„œ๋“œ ์ˆ˜ํ–‰ ์ „ ์บ์‹œ ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€

@CachePut

  • ์บ์‹œ์— ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ๋˜๋ฉฐ, ํ•ญ์ƒ ๋ฉ”์„œ๋“œ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์บ์‹œ ์•„์ดํ…œ์„ ์ €์žฅํ•œ๋‹ค.

Attribute Description

cacheNames ์บ์‹œ ์ด๋ฆ„
value cacheNames์˜ Alias
key SpEL ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•ด ์บ์‹œ ํ‚ค๋ฅผ ๋™์  ์ƒ์„ฑํ•˜๋„๋ก ํ•œ๋‹ค.
condition SpEL ํ‘œํ˜„์‹์œผ๋กœ ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์—ฌ true์ผ ๊ฒฝ์šฐ์—๋งŒ ์บ์‹ฑ์ด ์ ์šฉ๋˜๋„๋ก ํ•œ๋‹ค.
unless SpEL ํ‘œํ˜„์‹์œผ๋กœ ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์—ฌ true์ผ ๊ฒฝ์šฐ์— ์บ์‹ฑ์ด ์ ์šฉ๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
cacheManager ์‚ฌ์šฉํ•  CacheManager๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
cacheResolver ์‚ฌ์šฉํ•  CacheResolver๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Caching

  • ๋™์ผํ•œ ์บ์‹œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์—ฌ๋Ÿฌ ๊ฐœ ์„ ์–ธํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์กฐ๊ฑด์ด๋‚˜ ํ‚ค ํ‘œํ˜„ ๋ฐฉ์‹์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์œ ์šฉํ•˜๋‹ค.

Attribute Description

cacheable ์—ฌ๋Ÿฌ ๊ฐœ์˜ Cacheable ์–ด๋…ธํ…Œ์ด์…˜ ์ž…๋ ฅ
evict ์—ฌ๋Ÿฌ ๊ฐœ์˜ CacheEvict ์–ด๋…ธํ…Œ์ด์…˜ ์ž…๋ ฅ
put ์—ฌ๋Ÿฌ ๊ฐœ์˜ CachePut ์–ด๋…ธํ…Œ์ด์…˜ ์ž…๋ ฅ

@CacheConfig

  • ํด๋ž˜์Šค ์ˆ˜์ค€์˜ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ cacheNames, KeyGenerator ๋“ฑ์„ ์„ค์ •ํ•˜์—ฌ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
  • ํด๋ž˜์Šค ๋‚ด๋ถ€ ์บ์‹œ ์—ฐ์‚ฐ ์‹œ ์ด ์„ค์ •๊ฐ’์„ ๊ธฐ๋ณธ์œผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.
  • ๋ฉ”์„œ๋“œ ๋ ˆ๋ฒจ์—์„œ ์žฌ์ •์˜ํ•˜๋ฉด ๋ฎ์–ด์“ฐ์—ฌ์ง„๋‹ค.

Attribute Description

cacheNames ์บ์‹œ ์ด๋ฆ„
keyGenerator ์‚ฌ์šฉํ•  KeyGenerator๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
cacheManager ์‚ฌ์šฉํ•  CacheManager๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
cacheResolver ์‚ฌ์šฉํ•  CacheResolver๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ

์ „์ฒด์ ์ธ ๋™์ž‘์„ ๊ทธ๋ฆผ์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค. AOP์— ์˜ํ•ด Cacheable์ด ๋‹ฌ๋ฆฐ ํด๋ž˜์Šค๋Š” ํ”„๋ก์‹œ ํด๋ž˜์Šค๊ฐ€ ๋˜๊ณ , ์ด ํด๋ž˜์Šค์— ์˜ํ•ด CacheInterceptor์˜ invoke ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ์‹ค์ œ Cache Server์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๊ฒƒ์ด๋‹ค.

Configuration์œผ๋กœ ํ•„์š”ํ•œ ๋นˆ ๋“ฑ๋ก

@EnableCaching ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด CachingConfigurationSelector ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด Configuration์ด ๋“ฑ๋ก๋œ๋‹ค. ์ด ๋•Œ @EnableCaching ์—์„œ ์„ค์ •ํ•œ AdviceMode์— ๋”ฐ๋ผ PROXY, ASPECTJ ์ค‘ ํ•˜๋‚˜๋กœ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ๊ฐ Configuration ๋ชฉ๋ก์ด ์ œ๊ณต๋œ๋‹ค.

 

@EnableCaching ์—์„œ๋Š” Spring AOP์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” PROXY ๋ฐฉ์‹์„ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€๋งŒ ASPECTJ ๋ฐฉ์‹์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๊ฐ„๊ฐ„ํžˆ ์กด์žฌํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์ž๋ฉด, Spring AOP๋Š” ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๋Š”๋ฐ ๊ฐ™์€ ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— AOP ๋กœ์ง์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค. ASPECTJ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด weaving ๋ฐฉ์‹์œผ๋กœ ํด๋ž˜์ŠคํŒŒ์ผ(.class) ์ž์ฒด์— AOP ๋กœ์ง์„ ์ ์šฉํ•˜๋ฏ€๋กœ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. weaving ๋ฐฉ์‹์—๋Š” ์ˆ˜ํ–‰ ์‹œ์ ์— ๋”ฐ๋ผ compile-time, post-compile, load-time๋กœ ๋‚˜๋‰œ๋‹ค. ๋ณดํ†ต post-compile ๋ฐฉ์‹์€ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— weavingํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ๋‚ด๋ถ€ ํ˜ธ์ถœ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•ด์„œ๋Š” ๋‚˜๋จธ์ง€ ๋‘ ๋ฐฉ์‹์ด ์‚ฌ์šฉ๋œ๋‹ค. ๋งŒ์•ฝ compile-time์— weaving์ด ๋˜๋„๋ก ํ•˜๋ ค๋ฉด gradle, maven์—์„œ AspectJ๋ฅผ ์ง€์›ํ•˜๋Š” ๋ชจ๋“ˆ์„ ์ž„ํฌํŠธํ•˜๊ณ  AdviceMode๋ฅผ ASPECJ๋กœ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค. ํ˜น์€ load-time์— weaving์ด ๋˜๋„๋ก ํ•˜๋ ค๋ฉด AspectJ agent๋ฅผ ์‹คํ–‰ ์˜ต์…˜์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  @EnableLoadTimeWeaving ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด ๋œ๋‹ค.

 

PROXY ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๋“ฑ๋ก๋˜๋Š” ProxyCachingConfiguration ํด๋ž˜์Šค์˜ ๋‚ด๋ถ€์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์„ธ๊ฐ€์ง€ ๋นˆ์„ ๋“ฑ๋กํ•œ๋‹ค.

  • BeanFactoryCacheOperationSourceAdvisor
    • Spring Cache์—์„œ AOP๋กœ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ํฌ์ธํŠธ์ปท๊ณผ ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ๋“ฑ๋กํ•˜๋Š” ์–ด๋“œ๋ฐ”์ด์ €์ด๋‹ค.
    • @EnableCaching ์—์„œ ์„ค์ •ํ•œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์–ด๋“œ๋ฐ”์ด์Šค๊ฐ€ ํŠน์ • ์กฐ์ธ ํฌ์ธํŠธ์— ์ ์šฉ๋  ๊ฒฝ์šฐ ์–ด๋–ค ์ˆœ์„œ๋กœ ์ ์šฉํ•ด์•ผ ํ•  ์ง€ ์ง€์ •ํ•˜๋Š” order ์†์„ฑ์„ advisor ๋“ฑ๋ก ์‹œ์— ์‚ฌ์šฉํ•œ๋‹ค.
  • AnnotationCacheOperationSource
    • ํฌ์ธํŠธ์ปท์œผ๋กœ ๋“ฑ๋ก๋˜๋Š” CacheOperationSourcePointcut ํด๋ž˜์Šค์—์„œ Spring Cache ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๊ธฐ ์œ„ํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ ๊ฐ€์ง€๋Š” ํด๋ž˜์Šค์ด๋‹ค.
    • SpringCacheAnnotationParser๋ฅผ ํ†ตํ•ด ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฒ€์‚ฌํ•˜์—ฌ AOP ์ ์šฉ ๋Œ€์ƒ์ธ์ง€ ํ™•์ธํ•œ๋‹ค.
    • ์ ‘๊ทผ ์ œ์–ด์ž์— ๊ด€๊ณ„์—†์ด ๋ชจ๋‘ ํƒ์ƒ‰ํ•œ๋‹ค.
  • CacheInterceptor
    • ํฌ์ธํŠธ์ปท์— ์ ์šฉ๋  invoke ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•œ๋‹ค.
    • ์‹ค์งˆ์ ์ธ ๋กœ์ง์ด ์ž‘์„ฑ๋œ CacheAspectSupport ๋ฅผ ์ƒ์†๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—, ๊ตฌ์ฒด์ ์ธ ๋กœ์ง์€ ์ƒ์œ„ ํด๋ž˜์Šค์ธ CacheAspectSupport ํด๋ž˜์Šค์—๊ฒŒ ๋„˜๊ฒจ ์ฒ˜๋ฆฌํ•œ๋‹ค.

ProxyCachingConfiguration ๋‚ด๋ถ€์—์„œ ๋นˆ์„ ๋“ฑ๋กํ•˜๋Š” ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค.

@Bean(
  name = {"org.springframework.cache.config.internalCacheAdvisor"}
)
@Role(2)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
  BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
  advisor.setCacheOperationSource(cacheOperationSource);
  advisor.setAdvice(cacheInterceptor);
  if (this.enableCaching != null) {
    advisor.setOrder((Integer)this.enableCaching.getNumber("order"));
  }
  return advisor;
}

@Bean
@Role(2)
public CacheOperationSource cacheOperationSource() {
  return new AnnotationCacheOperationSource(false);
}

@Bean
@Role(2)
public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
  CacheInterceptor interceptor = new CacheInterceptor();
	interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
  interceptor.setCacheOperationSource(cacheOperationSource);
  return interceptor;
}

์–ด๋…ธํ…Œ์ด์…˜ ๋™์ž‘ ํ๋ฆ„

Spring Cache ์–ด๋…ธํ…Œ์ด์…˜์ด ๋‹ฌ๋ฆฐ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, Spring AOP์— ์˜ํ•ด ํ”„๋ก์‹œ ํด๋ž˜์Šค์—์„œ CacheInterceptor#invoke๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋œ๋‹ค. CacheInterceptor๋Š” ์บ์‹ฑ ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” CacheAspectSupport#execute ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆœ์„œ๋กœ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  1. CacheEvict(beforeInvoaction = true)
  2. Cacheable (get)
  3. ์›๋ณธ ๋ฉ”์„œ๋“œ invoke
  4. Cacheable (save) / CachePut
  5. CacheEvict(beforeInvoaction = false)

์•„๋ž˜๋Š” CacheAspectSupport์˜ ์ฝ”๋“œ๋กœ, 1~5์˜ ๊ณผ์ •์ด ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์ˆ˜ํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

@Nullable
private Object execute(CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    if (contexts.isSynchronized()) {
        // Special handling of synchronized invocation
        return executeSynchronized(invoker, method, contexts);
    }

    // 1) beforeInvocation์ด true์ธ Evict ์—ฐ์‚ฐ ์ˆ˜ํ–‰
    // Process any early evictions
    processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
            CacheOperationExpressionEvaluator.NO_RESULT);

    // Check if we have a cached value matching the conditions
    // 2) Cacheable์— ์˜ํ•œ ์บ์‹œ ์กฐํšŒ ์—ฐ์‚ฐ ์ˆ˜ํ–‰
    Object cacheHit = findCachedValue(invoker, method, contexts);
    if (cacheHit == null || cacheHit instanceof Cache.ValueWrapper) {
        // 3 ~ 5) ๋‚˜๋จธ์ง€ ์ž‘์—…์€ ์•„๋ž˜ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ์ˆ˜ํ–‰
        return evaluate(cacheHit, invoker, method, contexts);
    }
    return cacheHit;
}

// ...

@Nullable
private Object evaluate(@Nullable Object cacheHit, CacheOperationInvoker invoker, Method method,
        CacheOperationContexts contexts) {

    // Re-invocation in reactive pipeline after late cache hit determination?
    if (contexts.processed) {
        return cacheHit;
    }

    Object cacheValue;
    Object returnValue;

    if (cacheHit != null && !hasCachePut(contexts)) {
        // If there are no put requests, just use the cache hit
        cacheValue = unwrapCacheValue(cacheHit);
        returnValue = wrapCacheValue(method, cacheValue);
    }
    else {
    	// 3) ์›๋ณธ ๋ฉ”์„œ๋“œ ์ˆ˜ํ–‰
        // Invoke the method if we don't have a cache hit
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
    }

    // Collect puts from any @Cacheable miss, if no cached value is found
    List<CachePutRequest> cachePutRequests = new ArrayList<>(1);
    if (cacheHit == null) {
        collectPutRequests(contexts.get(CacheableOperation.class), cacheValue, cachePutRequests);
    }

    // Collect any explicit @CachePuts
    collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

    // Process any collected put requests, either from @CachePut or a @Cacheable miss
    for (CachePutRequest cachePutRequest : cachePutRequests) {
        // 4) Cacheable์—์„œ ์บ์‹œ ์กฐํšŒ์— ์‹คํŒจํ–ˆ๊ฑฐ๋‚˜ CachePut ์–ด๋…ธํ…Œ์ด์…˜์— ์˜ํ•œ ์บ์‹œ ์ €์žฅ ์—ฐ์‚ฐ ์ˆ˜ํ–‰
        Object returnOverride = cachePutRequest.apply(cacheValue);
        if (returnOverride != null) {
            returnValue = returnOverride;
        }
    }

    // Process any late evictions
    // 5) beforeInvocation์ด false์ธ Evict ์—ฐ์‚ฐ ์ˆ˜ํ–‰
    Object returnOverride = processCacheEvicts(
            contexts.get(CacheEvictOperation.class), false, returnValue);
    if (returnOverride != null) {
        returnValue = returnOverride;
    }

    // Mark as processed for re-invocation after late cache hit determination
    contexts.processed = true;

    return returnValue;
}

์บ์‹œ ์š”์ฒญ

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์‹ค์ œ ์บ์‹œ์— ์–ด๋–ป๊ฒŒ ์š”์ฒญ์ด ๋“ค์–ด๊ฐˆ๊นŒ? ์‹ค์ œ ์บ์‹œ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋กœ์ง์€ Cache ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์— ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค.

Redis์˜ ๊ฒฝ์šฐ์—๋„ Cache ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ธ RedisCache ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ put ๋ฉ”์„œ๋“œ๋งŒ ์‚ดํŽด๋ณด๋ฉด, Spring Data Redis ๋‚ด๋ถ€์ ์œผ๋กœ ์บ์‹œ์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” CacheWriter์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฒฐ๊ตญ Redis์— TCP ์š”์ฒญ์œผ๋กœ set ๋ช…๋ น์„ ๋ณด๋‚ด๋Š” ์ž‘์—…์ด ์ˆ˜ํ–‰๋œ๋‹ค.

public class RedisCache extends AbstractValueAdaptingCache {
	// ...
	@Override
	public void put(Object key, @Nullable Object value) {

		Object cacheValue = processAndCheckValue(value);

		byte[] binaryKey = createAndConvertCacheKey(key);
		byte[] binaryValue = serializeCacheValue(cacheValue);

		Duration timeToLive = getTimeToLive(key, value);
		// CacheWriter๋ฅผ ํ†ตํ•ด Redis์— ์‚ฝ์ž… ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
		getCacheWriter().put(getName(), binaryKey, binaryValue, timeToLive);
	}
	// ...
}

 

 

์‹ค์ œ ์บ์‹œ ์ €์žฅ์†Œ์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” Cache ๊ตฌํ˜„์ฒด์˜ ๋ฉ”์„œ๋“œ๋Š” CacheAspectSupport๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค. ์•„๋ž˜๋Š” findCachedValue ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์ ์œผ๋กœ ํ˜ธ์ถœ๋˜๋Š” findInCaches๋ผ๋Š” ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๋กœ, AbstractCacheInvoker์˜ doRetrieve ๋ฉ”์„œ๋“œ ํ˜น์€ doGet ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ๋‹ค.  Cache ๊ตฌํ˜„์ฒด์˜ ๋ฉ”์„œ๋“œ๋Š” AbstractCacheInvoker์˜ doGet, doRetrieve, doPut, doClear ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋“ค์—์„œ ํ˜ธ์ถœ๋œ๋‹ค.

public abstract class CacheAspectSupport extends AbstractCacheInvoker
		implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
    
	// ...

	private Object findInCaches(CacheOperationContext context, Object key,
			CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {

		for (Cache cache : context.getCaches()) {
			if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
				CompletableFuture<?> result = doRetrieve(cache, key);
				if (result != null) {
					return result.exceptionally(ex -> {
						getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
						return null;
					}).thenCompose(value -> (CompletableFuture<?>) evaluate(
							(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
							invoker, method, contexts));
				}
				else {
					continue;
				}
			}
			if (this.reactiveCachingHandler != null) {
				Object returnValue = this.reactiveCachingHandler.findInCaches(
						context, cache, key, invoker, method, contexts);
				if (returnValue != ReactiveCachingHandler.NOT_HANDLED) {
					return returnValue;
				}
			}
			// ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋™๊ธฐ ๋ฐฉ์‹์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.
			Cache.ValueWrapper result = doGet(cache, key);
			if (result != null) {
				return result;
			}
		}
		return null;
	}
    
	// ...
}

 

public abstract class AbstractCacheInvoker {
	// ...
	@Nullable
	protected <T> T doGet(Cache cache, Object key, Callable<T> valueLoader) {
		try {
			// Cache ๊ตฌํ˜„์ฒด์˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
			return cache.get(key, valueLoader);
		}
		catch (Cache.ValueRetrievalException ex) {
			throw ex;
		}
		catch (RuntimeException ex) {
			getErrorHandler().handleCacheGetError(ex, cache, key);
			try {
				return valueLoader.call();
			}
			catch (Exception ex2) {
				throw new RuntimeException(ex2);
			}
		}
	}
	// doPut, doClear ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•œ๋‹ค...
}

 

๋งˆ๋ฌด๋ฆฌ

์ง€๊ธˆ๊นŒ์ง€ Spring Cacheable์˜ ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด์•˜๋‹ค. Spring AOP๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ชจ๋ฅธ๋‹ค๋ฉด ๋””๋ฒ„๊น…ํ•  ๋•Œ ํ—ค๋งฌ ์ˆ˜ ์žˆ๋‹ค. ๋น„๋ก ๊ฐ„๋‹จํ•œ ์—ฐ์‚ฐ๋งŒ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ์ถ”์ƒํ™”๋˜์–ด ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ๋•๋ถ„์— ์บ์‹œ ์†”๋ฃจ์…˜์„ ๊ต์ฒดํ•  ๋•Œ ์บ์‹œ ์ ์šฉ ์ฝ”๋“œ๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ๋นˆ ์„ค์ •๋งŒ ๋ณ€๊ฒฝํ•˜์—ฌ ์‰ฝ๊ฒŒ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค.

๋Œ“๊ธ€