Spring でデータキャッシング

このガイドでは、Spring 管理の Bean でキャッシュを有効にするプロセスについて説明します。

構築するもの

簡単な本リポジトリでキャッシュを有効にするアプリケーションを構築します。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-caching/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックして、Spring cache abstraction を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。

ブックモデルを作成する

まず、あなたの本のためにシンプルなモデルを作成する必要があります。次のリスト(src/main/java/com/example/caching/Book.java から)は、その方法を示しています。

package com.example.caching;

public class Book {

  private String isbn;
  private String title;

  public Book(String isbn, String title) {
    this.isbn = isbn;
    this.title = title;
  }

  public String getIsbn() {
    return isbn;
  }

  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  @Override
  public String toString() {
    return "Book{" + "isbn='" + isbn + '\'' + ", title='" + title + '\'' + '}';
  }

}

ブックリポジトリを作成する

そのモデルのリポジトリも必要です。次のリスト(src/main/java/com/example/caching/BookRepository.java から)は、そのようなリポジトリを示しています。

package com.example.caching;

public interface BookRepository {

  Book getByIsbn(String isbn);

}

{SpringData}[Spring Data] を使用して、広範囲の SQL ストアまたは NoSQL ストアにリポジトリの実装を提供することもできます。ただし、このガイドの目的では、一部の遅延 (ネットワークサービス、遅い遅延、その他の課題) をシミュレートする単純な実装を使用するだけです。次のリスト ( src/main/java/com/example/caching/SimpleBookRepository.java から) は、そのようなリポジトリを示しています。

package com.example.caching;

import org.springframework.stereotype.Component;

@Component
public class SimpleBookRepository implements BookRepository {

  @Override
  public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
  }

  // Don't do this at home
  private void simulateSlowService() {
    try {
      long time = 3000L;
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalStateException(e);
    }
  }

}

simulateSlowService は、各 getByIsbn 呼び出しに意図的に 3 秒の遅延を挿入します。後で、この例をキャッシュで高速化します。

リポジトリの使用

次に、リポジトリを接続し、それを使用していくつかの書籍にアクセスする必要があります。次のリスト(src/main/java/com/example/caching/CachingApplication.java から)は、その方法を示しています。

package com.example.caching;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CachingApplication {

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

}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

また、BookRepository を挿入し、異なる引数で複数回呼び出す CommandLineRunner も必要です。次のリスト(src/main/java/com/example/caching/AppRunner.java から)は、そのクラスを示しています。

package com.example.caching;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements CommandLineRunner {

  private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);

  private final BookRepository bookRepository;

  public AppRunner(BookRepository bookRepository) {
    this.bookRepository = bookRepository;
  }

  @Override
  public void run(String... args) throws Exception {
    logger.info(".... Fetching books");
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
  }

}

この時点でアプリケーションを実行しようとすると、まったく同じ本を数回取得しているにもかかわらず、非常に遅いことに気付くはずです。次の出力例は、(意図的にひどい)コードが作成した 3 秒の遅延を示しています。

2014-06-05 12:15:35.783  ... : .... Fetching books
2014-06-05 12:15:40.783  ... : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2014-06-05 12:15:43.784  ... : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2014-06-05 12:15:46.786  ... : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}

キャッシュを有効にすることで状況を改善できます。

キャッシュを使用可能にする

これで、SimpleBookRepository でキャッシュを有効にして、ブックが books キャッシュ内にキャッシュされるようにすることができます。次のリスト(src/main/java/com/example/caching/SimpleBookRepository.java から)は、リポジトリ定義を示しています。

package com.example.caching;

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

@Component
public class SimpleBookRepository implements BookRepository {

  @Override
  @Cacheable("books")
  public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
  }

  // Don't do this at home
  private void simulateSlowService() {
    try {
      long time = 3000L;
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalStateException(e);
    }
  }

}

次の例(src/main/java/com/example/caching/CachingApplication.java から)が行う方法を示すように、キャッシュアノテーションの処理を有効にする必要があります。

package com.example.caching;

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

@SpringBootApplication
@EnableCaching
public class CachingApplication {

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

}

@EnableCaching (Javadoc) アノテーションは、public メソッドのキャッシュアノテーションの存在についてすべての Spring Bean をインスペクションするポストプロセッサーをトリガーします。そのようなアノテーションが見つかった場合、メソッド呼び出しをインターセプトし、それに応じてキャッシュ動作を処理するプロキシが自動的に作成されます。

ポストプロセッサーは、@Cacheable (Javadoc) @CachePut (Javadoc) @CacheEvict (Javadoc) アノテーションを処理します。詳細については、Javadoc およびリファレンスガイドを参照してください

Spring Boot は、適切な CacheManager (Javadoc) を自動的に構成して、関連するキャッシュのプロバイダーとして機能します。詳細については、Spring Boot のドキュメントを参照してください。

サンプルでは特定のキャッシュライブラリを使用しないため、キャッシュストアは ConcurrentHashMap を使用する単純なフォールバックです。キャッシング抽象化は、幅広いキャッシュライブラリをサポートし、JSR-107(JCache)に完全に準拠しています。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-caching-0.1.0.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-caching-0.1.0.jar
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。

アプリケーションをテストする

キャッシュが有効になったため、アプリケーションを再度実行し、同じ ISBN の有無に関係なく追加の呼び出しを追加することで違いを確認できます。それは大きな違いを生むはずです。次のリストは、キャッシュが有効になっている出力を示しています。

2016-09-01 11:12:47.033  .. : .... Fetching books
2016-09-01 11:12:50.039  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2016-09-01 11:12:53.044  .. : isbn-4567 -->Book{isbn='isbn-4567', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-4567 -->Book{isbn='isbn-4567', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
2016-09-01 11:12:53.045  .. : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}

上記の出力例では、書籍の最初の取得にはまだ 3 秒かかります。ただし、同じ本の 2 回目以降ははるかに高速であり、キャッシュが機能していることを示しています。

要約

おめでとう! Spring が管理する Bean でキャッシュを有効にしました。

関連事項

次のガイドも役立ちます。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。

コードを入手する