前回、インターセプターを使ってリクエスト処理の共通化を行う方法を学びました。
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);
}
}
- 動作:
getItemDetail(10L)が呼ばれる。- キャッシュ items の中を、引数 10L をキーとして探す。
- **(初回)**キャッシュになければ、メソッドを実行(DBアクセスが発生)。結果をキャッシュに保存し、呼び出し元に返す。
- (二回目以降)キャッシュにあれば、メソッドを実行せずにキャッシュの値を返し、高速に処理が完了する。
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回】システムを疎結合にする!〜イベント駆動型アーキテクチャの基本〜 にご期待ください!


コメント