⚡️ Spring Framework 講座【第45回】応答速度の鍵!〜Spring Cacheの基本と活用〜

docs

前回、インターセプターを使ってリクエスト処理の共通化を行う方法を学びました。

Webアプリケーションの性能(パフォーマンス)を向上させる上で、最も効果的かつシンプルな手段の一つが**キャッシュ(Cache)**の利用です。特に、データベースへのアクセスや外部APIの呼び出しなど、時間のかかる処理の結果を一時的に保存しておくと、アプリケーションの応答速度は劇的に向上します。

今回は、Spring Frameworkが提供する強力な抽象化機能である **Spring Cache Abstraction(Springキャッシュ抽象化)**と、その基本的なアノテーションの使い方を学びます。


1. キャッシュとは?

キャッシュとは、「一度取得したデータや、計算した結果をメモリなどの高速な場所に一時的に保存しておき、次回同じデータが要求されたときに、時間のかかる元の処理を実行せずに、保存しておいたデータを返す仕組み」です。

1-1. キャッシュのメリットと注意点

項目説明
メリット応答速度の向上、データベースや外部サービスへの負荷軽減。
注意点鮮度(Staleness)の問題。元のデータが更新されたにも関わらず、古いキャッシュが返されることがあるため、適切な有効期限や**削除(Eviction)**の仕組みが必要。

2. Spring Cache Abstractionの基本

Spring Cache Abstractionは、開発者が具体的なキャッシュライブラリ(Redis、Ehcacheなど)を意識することなく、アノテーションを使ってキャッシュ操作をコードに組み込むための仕組みです。

2-1. キャッシュ機能の有効化

まず、Spring Bootにキャッシュ機能を使いますよ、と伝えます。設定クラスに @EnableCaching を付与するだけです。

Java

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching // キャッシュ機能を有効化
public class CacheConfig {
    // 実際には、この設定クラスでキャッシュマネージャ(Redisなど)のBeanを設定します
}

2-2. データの取得とキャッシュ保存:@Cacheable

最も基本的なアノテーションで、「このメソッドの実行結果をキャッシュに保存し、次回以降はキャッシュから返す」という動作を定義します。

Java

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

@Service
public class ItemService {
    
    // itemsという名前のキャッシュ領域を使用する
    @Cacheable("items") 
    public Item getItemDetail(Long itemId) {
        System.out.println("DBからデータを取得中: " + itemId); 
        // 実際にはRepositoryを呼び出す処理
        // ここに時間のかかるDBアクセスや外部API呼び出しが入る
        
        return itemRepository.findById(itemId).orElse(null);
    }
}
  • 動作:
    1. getItemDetail(10L) が呼ばれる。
    2. キャッシュ items の中を、引数 10L をキーとして探す。
    3. **(初回)**キャッシュになければ、メソッドを実行(DBアクセスが発生)。結果をキャッシュに保存し、呼び出し元に返す。
    4. (二回目以降)キャッシュにあれば、メソッドを実行せずにキャッシュの値を返し、高速に処理が完了する。

2-3. キャッシュキーのカスタマイズ

デフォルトでは、メソッドの全ての引数を組み合わせてキーが生成されます。特定の引数だけを使いたい場合は、key 属性で指定します。

Java

// itemIdだけをキーとして使用。userIdが変わってもキャッシュは再利用される
@Cacheable(value = "items", key = "#itemId") 
public Item getItemDetail(Long itemId, Long userId) { /* ... */ } 

3. キャッシュの更新と削除

データが更新されたり、不要になったりしたときは、キャッシュを削除(Eviction)または更新する必要があります。

3-1. データの更新とキャッシュ更新:@CachePut

メソッドを実行し、その**結果を常にキャッシュに保存(更新)**します。既存のキャッシュキーが存在しなくても上書きされます。

Java

@CachePut(value = "items", key = "#item.id") 
public Item updateItem(Item item) {
    // 常にDB更新処理を実行する
    Item savedItem = itemRepository.save(item); 
    // DB更新後の最新の結果をキャッシュに保存する
    return savedItem;
}

3-2. データの削除:@CacheEvict

メソッドが実行された(または)に、キャッシュから指定したデータを削除します。

Java

@CacheEvict(value = "items", key = "#itemId") 
public void deleteItem(Long itemId) {
    // DB削除処理を実行
    itemRepository.deleteById(itemId); 
    // 削除後、キャッシュから該当キーのデータを削除
}

// キャッシュ領域全体をクリアする場合
@CacheEvict(value = "items", allEntries = true) 
public void clearAllCache() {
    // ...
}

4. 実際のキャッシュマネージャ(Redisとの連携)

Spring Cache Abstractionは、あくまで「抽象化」です。実際にキャッシュデータを保存・管理するキャッシュマネージャが必要です。

  • 開発環境: ConcurrentMapCacheManager(インメモリキャッシュ)がデフォルトで使われ、簡単に試せます。
  • 本番環境: アプリケーションがスケールしたり、複数のインスタンスで実行される場合は、外部の分散キャッシュシステムが必要です。**Redis(レディス)**が最も一般的です。

Redisを使うには、依存関係に spring-boot-starter-data-redis を追加し、application.yml でRedisの接続情報を設定するだけで、Spring Bootが自動的に RedisCacheManager を使用するようになります。

✅ 本日のまとめ

  • キャッシュは、時間のかかる処理の結果を保存し、応答速度の向上と負荷軽減を実現する。
  • @EnableCaching でSpring Cache機能を有効化する。
  • @Cacheable は、メソッドの結果をキャッシュに保存し、次回呼び出し時にキャッシュから返す。
  • @CachePut は、メソッドの結果でキャッシュを常に更新する。
  • @CacheEvict は、メソッドの実行後にキャッシュを削除する。
  • 本番環境では、スケーラビリティのためにRedisなどの分散キャッシュシステムとの連携が推奨される。

🔔 次回予告

これで、Springを使ったアプリケーション開発と運用の主要なトピックはすべて完了しました。

次回からは、システムの高度な設計と、Javaのリアクティブプログラミングといった、より専門性の高いトピックに進みます。次回は、システムの疎結合性を高めるための設計パターンであるイベント駆動型アーキテクチャの基本を学びます。

次回:【第46回】システムを疎結合にする!〜イベント駆動型アーキテクチャの基本〜 にご期待ください!

コメント

タイトルとURLをコピーしました