SpringのCache機能をサクッと利用する

Spring(Spring Boot)のCache機能を使ってメソッドの呼び出し結果をキャッシュしておくサンプルです。

# 動作確認バージョン

* Spring Boot 2.7.5
* Spring Framework 5.3.23

# Cache機能の有効化

`@EnableCaching` を付与するとSpring Bootさんの自動コンフィギュレーション機能によってSpringのCache機能が利用できるようになります。

```java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching // ★★★アノテーションを追加
public class SpringCacheDemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringCacheDemoApplication.class, args);
  }

}
```

# キャッシュ対象のメソッドを指定

結果をキャッシュしたいメソッドに`@Cacheable`を付与する。下記のサンプルではキャッシュ名を「`rates`」にしています。

```java
package com.example.demo;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.UUID;

@Component
public class RateRepository {

  @Cacheable("rates") // ★★★ キャッシュしたいメソッドにアノテーションを追加
  public Rate getRate(String unitId, String groupId, String type, String baseDate) {
    Rate rate = new Rate();
    rate.setId(UUID.randomUUID().toString());
    rate.setRate(BigDecimal.valueOf(new SecureRandom().nextLong()));
    return rate;
  }

}
```

# 動かしてみる

以下のJUnitを動かすと、同じパラメータでメソッドを呼び出した場合は、キャッシュ済みのインスタンスが返却されることがわかります。

```java
@SpringBootTest
class SpringCacheDemoApplicationTests {

  @Autowired
  RateRepository rateRepository;

  @Test
  void contextLoads() {
    Rate rate1 = rateRepository.getRate("U001", "G001", "Credit", "20221114");
    Rate rate2 = rateRepository.getRate("U001", "G001", "Credit", "20221114");

    // ★★★ 同じインスタンスが返却される
    Assertions.assertThat(rate2.getId()).isEqualTo(rate1.getId());
    Assertions.assertThat(rate2.getRate()).isEqualTo(rate1.getRate());
    Assertions.assertThat(rate2).isSameAs(rate1);
  }

}
```


# テストクラスでキャッシュを削除する

テストの場合は、他のテストに影響を与えないためにキャッシュ済みのデータをテスト毎に破棄しておいた方が良いので・・・テストクラスに`CacheManager`をインジェクションしてキャッシュをクリアしておきます。

```java
@SpringBootTest
class SpringCacheDemoApplicationTests {

  @Autowired
  RateRepository rateRepository;

  @Autowired
  CacheManager cacheManager;

  // ★★★ テスト実施前後にキャッシュをクリアする
  @BeforeEach
  @AfterEach
  void clearCache() {
    cacheManager.getCacheNames().stream()
        .map(cacheManager::getCache)
        .filter(Objects::nonNull)
        .forEach(Cache::clear);
  }

  @Test
  void contextLoads() {
    Rate rate1 = rateRepository.getRate("U001", "G001", "Credit", "20221114");
    Rate rate2 = rateRepository.getRate("U001", "G001", "Credit", "20221114");

    Assertions.assertThat(rate2.getId()).isEqualTo(rate1.getId());
    Assertions.assertThat(rate2.getRate()).isEqualTo(rate1.getRate());
    Assertions.assertThat(rate2).isSameAs(rate1);

    // ★★★ 明示的にキャッシュを削除してみる
    clearCache();

    // ★★★ キャッシュ削除後に同じパラメータでメソッドを呼び出しみる
    Rate rate3 = rateRepository.getRate("U001", "G001", "Credit", "20221114");

    // ★★★ Cacheクリア後は別のインスタンスが返却される
    Assertions.assertThat(rate3.getId()).isNotEqualTo(rate1.getId());
    Assertions.assertThat(rate3.getRate()).isNotEqualTo(rate1.getRate());
    Assertions.assertThat(rate3).isNotSameAs(rate1);
  }

}
```

# 参考ページ

* https://docs.spring.io/spring-boot/docs/2.7.5/reference/htmlsingle/#io.caching
* https://docs.spring.io/spring-framework/docs/5.3.23/reference/html/integration.html#cache