Spring Bootでのキャッシュのアノテーションの使い方

はじめに

こんにちは、新卒1年目の岡崎です。エキサイトホールディングス Advent Calendar 2023の14日目を担当します。

今回は、Spring Bootで使うことができるキャッシュのアノテーションの機能を紹介していきたいと思います。

環境

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.6)
## Java

openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode)
------------------------------------------------------------
Gradle 7.4.1
------------------------------------------------------------

Build time:   2022-03-09 15:04:47 UTC
Revision:     36dc52588e09b4b72f2010bc07599e0ee0434e2e

Kotlin:       1.5.31
Groovy:       3.0.9
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.2 (Eclipse Adoptium 17.0.2+8)
OS:           Mac OS X 12.3 aarch64

設定

キャッシュを使うために、以下の設定を行う必要があります。

  • build.gradleの設定
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
}
  • application.ymlの設定
spring:
  redis:
    host: localhost
    port: 6379
  • キャッシュをする時に、返り値として使用するモデルにSerializableを継承させる
@Data
@Accessors(chain = true)
public class SampleModel implements Serializable {
    // 要素を記述    
}

どのようにSpring Bootでキャッシュのアノテーションをつけるのか

Spring Bootでは、アノテーションをつけることで、キャッシュをすることができます。
今回は、Spring Bootで用意されているキャッシュに関するアノテーションを三つ紹介します。

@Cacheable

@Cacheableをメソッドにつけることで、返り値をキャッシュすることができます。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

デフォルトでは、キャッシュの名前とメソッドの引数をキーとして、返り値を保存しています。

@CachePut

@CachePutでは、キーが同じキャッシュの返り値を更新することができます。キーはキャッシュ名とメソッドの引数を一致させる必要がありますが、アノテーションで用いるkeyというアトリビュートに値を入れることで、メソッドの引数の代わりに使用することができます。

この時に新しく更新される値は、@CachePutのアノテーションがついているメソッドの返り値になります。

@Cacheable(cacheNames = "GET_BOOK")
public Book getBook(Integer bookId) {
    // IDから本の情報を取得する
}

@CachePut(cacheNames = "GET_BOOK", key = "#book.getBookID()")
public Book updateBook(Book book) {
    // 本の情報を更新する
} 

@CacheEvict

@CacheEvictでは、キーが同じキャッシュを削除することができます。

@CacheEvict(cacheNames = "GET_BOOK", key = "#book.getBookID()")
public void updateBook(Book book) {
    // 本の情報を更新する
} 

オプション

@Cacheable、@CachePut、@CacheEvictでは、それぞれオプションが用意されています。ここでは、オプションを三つ紹介します。

key

keyというアトリビュートに任意の変数を入れることで、キャッシュのキーに使うメソッドの引数を自由に設定することができます。
以下は、キャッシュをする時に、メソッドの引数の中でもtypeとsizeだけ使用したい場合の一例の実装です。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST", key = "{#type, #size}")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

condition

conditionというアトリビュートに条件式を書くことで、特定の値であれば、キャッシュをするように動作することが可能です。
以下は、useCacheがtrueの時だけキャッシュをしたい場合の実装例です。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST", key = "{#type, #size}", condition = "#useCache")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

unless

unlessというアトリビュートに条件式を書くことで、特定の値であれば、キャッシュの動作をしないようにすることが可能です。
以下は、返ってきた値が空だった場合、キャッシュしないようにする場合の実装例です。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST", key = "{#type, #size}", unless = "#result.isEmpty()")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

補足: キャッシュはいつつけるのか?

初心者の場合、キャッシュをつければ良いのか、それともつけなくて良いのか、よく分からない場合があります。(少なくとも私はそうでした。)キャッシュの実装の有無を検討する時の一例を、補足として残しておきます。

結論から言うと、キャッシュはヒット率が高い時に有効です。

例えば、1分間に10人のユーザーが「ジャンルごとの本の一覧」を見ていて、その中でもAジャンルを7人が見ているとします。 この場合、同じクエリーパラメーターのアクセスが7割も来ているので、ヒット率は高いと言えそうです。

しかし、1分間に20人のユーザーが「検索」機能を使っていても、この中で誰一人として同じキーワードを使っていなかった場合、ヒット率は低いでしょう。

このように、アクセスが多くても、ユーザーが同じキーワードを使用しないような検索の場合は、キャッシュをするように実装を行っても、DBのアクセス量がほとんど変わらない可能性があります。 このような場合、キャッシュをする必要がないかもしれません。

同じクエリーパラメーターを持つアクセスがどれくらいあるかを検討し、その結果に基づいてキャッシュをするように実装を行うかどうか決めるといいかもしれません。

最後に

今回は、Spring Bootで使うことができるキャッシュのアノテーションの使い方を紹介しました。改めてキャッシュを適切に実装することが大切だと感じました。ここまで読んでいただきありがとうございました。