© 2011-2021 The original authors.

このドキュメントのコピーは、あなた自身の使用および他者への配布のために作成することができますが、そのようなコピーに料金を請求しないこと、さらに、印刷物または電子的に配布されるかどうかにかかわらず、各コピーにこの著作権表示が含まれていることを条件とします。

序文

Spring Data Redis プロジェクトは、キーバリュースタイルのデータストアを使用して、コア Spring の概念をソリューションの開発に適用します。メッセージを送受信するための高レベルの抽象化として「テンプレート」を提供します。Spring Framework での JDBC サポートとの類似点に気付くかもしれません。

このセクションでは、Spring Data Redis モジュールの使用を開始するためのわかりやすいガイドを提供します。

1. Spring の学習

Spring Data は、以下を含む Spring フレームワークのコア機能を使用します。

Spring API を知る必要はありませんが、その背景となる概念を理解することは重要です。最低限、Inversion of Control (IoC) の背景となる概念は知っておくべきであり、使用する IoC コンテナーにも精通している必要があります。

Redis サポートのコア機能は、Spring コンテナーの IoC サービスを呼び出す必要なしに、直接使用できます。これは JdbcTemplate によく似ており、Spring コンテナーの他のサービスなしで「スタンドアロン」で使用できます。リポジトリのサポートなど、Spring Data Redis のすべての機能を活用するには、Spring を使用するようにライブラリの一部を構成する必要があります。

Spring の詳細については、Spring Framework を詳細に説明する包括的なドキュメントを参照してください。このテーマに関する記事、ブログのエントリ、本はたくさんあります。詳細については、Spring フレームワークのホームページを参照してください。

一般に、これは Spring Data Redis を試してみたい開発者にとっての出発点となるはずです。

2. NoSQL と KeyValue ストアの学習

NoSQL ストアはストレージの世界を席巻しました。これは、ソリューション、用語、パターンが多数ある広大なドメインです(さらに悪いことに、用語自体にも複数の意味があります (英語) )。いくつかの原則は一般的ですが、SDR でサポートされているストアにある程度精通していることが重要です。これらのソリューションに精通するための最良のメソッドは、ドキュメントを読み、例に従うことです。通常、通過するのに 5 〜 10 分以上かかることはなく、RDMBS のみのバックグラウンドから来た場合、これらの演習は多くの場合、目を見張るものになる可能性があります。

2.1. サンプルを試す

専用の Spring Data サンプルリポジトリ(https://github.com/spring-projects/spring-data-keyvalue-examples (英語) )で、Key-Value ストアのさまざまなサンプルを見つけることができます。Spring Data Redis の場合、ローカルで実行したりクラウドにデプロイしたりできる Redis 上に構築された Twitter クローンである retwisj サンプルに特に注意を払う必要があります。詳細については、そのドキュメント (英語) 、次のブログエントリ (英語) を参照してください。

3. 要件

Spring Data Redis 2.x バイナリには、JDK レベル 8.0 以上および Spring Framework 5.3.3 以上が必要です。

Key-Value ストアに関しては、Redis (英語) 2.6.x 以降が必要です。Spring Data Redis は現在、最新の 4.0 リリースに対してテストされています。

4. 追加のヘルプリソース

新しいフレームワークを学ぶことは必ずしも簡単ではありません。このセクションでは、Spring Data Redis モジュールを開始するためのわかりやすいガイドと思われるものを提供しようとしています。ただし、問題が発生した場合やアドバイスが必要な場合は、次のいずれかのリンクを使用してください。

コミュニティフォーラム

スタックオーバーフロー (英語) 上の Spring Data は、すべての Spring Data(ドキュメントだけではない)ユーザーが情報を共有し、互いに助け合うためのタグです。登録は投稿にのみ必要です。

専門サポート

Spring Data および Spring を開発している Pivotal Sofware, Inc では、レスポンス時間が保証されたプロフェッショナルなソースからのサポートを提供しています。

5. 開発のフォロー

Spring Data ソースコードリポジトリ、ナイトリービルド、スナップショットアーティファクトの詳細については、Spring Data ホームページを参照してください

spring-data (英語) または spring-data-redis (英語) のいずれかで StackOverflow の開発者とやり取りすることで、Spring Data を Spring コミュニティのニーズに最適に対応させることができます。

バグに遭遇した場合、または改善を提案したい場合(このドキュメントを含む)、Github (英語) でチケットを作成してください。

Spring エコシステムの最新ニュースや発表を最新の状態に保つには、Spring コミュニティポータルに登録してください。

最後に、Twitter で Spring ブログ (英語) またはプロジェクトチーム(@SpringData (英語) )をフォローできます。

6. 注目の新機能

このセクションでは、最新リリースで新しく注目に値する項目について簡単に説明します。

6.1. Spring Data の新機能 Redis 2.4

  • RedisCache が CacheStatistics を公開するようになりました。

  • Redis スタンドアロン、Redis クラスターおよびマスター / レプリカの ACL 認証サポート。

  • Jedis を使用した Redis Sentinel のパスワードサポート。

  • ZREVRANGEBYLEX および ZLEXCOUNT コマンドのサポート。

6.2. Spring Data の新機能 Redis 2.3

  • Duration および Instant のテンプレート API メソッドの改善。

  • ストリームコマンドの拡張。

6.3. Spring Data の新機能 Redis 2.2

  • Redis ストリーム

  • 単一のキーのコレクションを受け入れる洗練された union/diff/intersect セット操作メソッド。

  • Jedis3 にアップグレードします。

  • JedisCluster を使用したスクリプトコマンドのサポートを追加します。

6.4. Spring Data の新機能 Redis 2.1

  • Lettuce を使用した Unix ドメインソケット接続。

  • Lettuce を使用したマスターに書き込み、レプリカから読み取りますサポート。

  • 例示による問い合わせ統合。

  • @TypeAlias Redis リポジトリのサポート。

  • 両方のドライバーでサポートされている選択されたノードで Lettuce および SCAN を使用するクラスター全体の SCAN

  • メッセージストリームを送受信するためのリアクティブ Pub/Sub

  • BITFIELDBITPOS、および OBJECT コマンドのサポート。

  • BoundZSetOperations の戻り値の型を ZSetOperations に合わせます。

  • リアクティブ SCANHSCANSSCAN、および ZSCAN のサポート。

  • リポジトリクエリメソッドでの IsTrue および IsFalse キーワードの使用。

6.5. Spring Data の新機能 Redis 2.0

  • Java 8 にアップグレードします。

  • Lettuce 5.0. にアップグレードする

  • SRP および JRedis ドライバーのサポートを削除しました。

  • Lettuce を使用したリアクティブ接続のサポート

  • RedisConnection 用の Redis 機能固有のインターフェースを紹介します。

  • JedisClientConfiguration および LettuceClientConfiguration による RedisConnectionFactory 構成の改善。

  • RedisCache の実装を改訂しました。

  • Redis 3.2. のカウントコマンドで SPOP を追加する

6.6. Spring Data の新機能 Redis 1.8

  • Jedis2.9 にアップグレードします。

  • Lettuce 4.2 にアップグレードします(注:Lettuce 4.2 には Java 8 が必要です)。

  • Redis GEO (英語) コマンドのサポート。

  • Spring Data リポジトリの抽象化を使用した地理空間インデックスのサポート(地理空間インデックスを参照)。

  • MappingRedisConverter ベースの HashMapper 実装(ハッシュマッピングを参照)。

  • リポジトリでの PartialUpdate のサポート(部分的な更新の永続化を参照)。

  • Redis クラスターへの接続の SSL サポート。

  • Jedis 使用時の client name から ConnectionFactory のサポート。

6.7. Spring Data の新機能 Redis 1.7

6.8. Spring Data の新機能 Redis 1.6

  • Lettuce Redis ドライバーが wg / レタス: GitHub (英語) から mp911de / レタス: GitHub (英語) に切り替わりました。

  • ZRANGEBYLEX のサポート。

  • +inf / -inf を含む ZSET の拡張範囲操作。

  • RedisCache のパフォーマンスが向上し、接続が早期に解放されるようになりました。

  • Jackson のポリモーフィックデシリアライズを利用する汎用 Jackson2 RedisSerializer

6.9. Spring Data の新機能 Redis 1.5

  • Redis のサポートを追加します。HyperLogLog コマンド: PFADDPFCOUNT、および PFMERGE

  • Jackson ベースの RedisSerializers の構成可能な JavaType ルックアップ。

  • Redis に接続するための PropertySource ベースの構成 Sentinel(参照:Redis Sentinel サポート)。

7. 依存関係

個々の Spring Data モジュールの開始日が異なるため、それらのほとんどは異なるメジャーバージョン番号とマイナーバージョン番号を持っています。互換性のあるものを見つける最も簡単な方法は、互換性のあるバージョンが定義された状態で提供される Spring Data リリーストレイン BOM に依存することです。Maven プロジェクトでは、次のように POM の <dependencyManagement /> セクションでこの依存関係を宣言します。

例 1: Spring Data リリーストレイン BOM の使用
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2021.0.0-M2</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

現在のリリーストレインバージョンは 2021.0.0-M2 です。トレインバージョンでは、パターン YYYY.MINOR.MICROcalver (英語) を使用しています。バージョン名は、GA リリースとサービスリリースでは ${calver} に従い、他のすべてのバージョンでは次のパターンに従います。${calver}-${modifier}modifier は次のいずれかになります。

  • SNAPSHOT: 現在のスナップショット

  • M1M2 など:マイルストーン

  • RC1RC2 など:リリース候補

Spring Data サンプルリポジトリ: GitHub (英語) で BOM の使用例を見つけることができます。これが適切な場所にあると、次のように、<dependencies /> ブロックでバージョンなしで使用する Spring Data モジュールを宣言できます。

例 2: Spring Data モジュールへの依存関係の宣言
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

7.1. Spring Boot を使用した依存関係管理

Spring Boot は、Spring Data モジュールの最新バージョンを選択します。それでも新しいバージョンにアップグレードする場合は、spring-data-releasetrain.version プロパティを使用するトレインバージョンとイテレーションに設定します。

7.2. Spring Framework

Spring Data モジュールの現在のバージョンには、Spring Framework 5.3.3 以降が必要です。モジュールは、そのマイナーバージョンの古いバグ修正バージョンでも動作する可能性があります。ただし、その世代内の最新バージョンを使用することを強くお勧めします。

リファレンスドキュメント

8. 導入

8.1. ドキュメント構造

リファレンスドキュメントのこのパートでは、Spring Data Redis が提供するコア機能について説明しています。Key-Value モジュールの概念とセマンティクス、およびさまざまなストア名前空間の構文について説明します。Key-Value ストア、Spring、または Spring Data の例の概要については、NoSQL と KeyValue ストアの学習を参照してください。このドキュメントは、Spring Data Redis サポートのみに言及しており、ユーザーが Key-Value ストレージと Spring の概念に精通していることを前提としています。

Redis サポート」では、Redis モジュールの機能セットを紹介しています。

Redis リポジトリ」では、Redis のリポジトリサポートが導入されています。

このドキュメントは、Spring Data Redis(SDR)サポートのリファレンスガイドです。

9. なぜ Spring Data Redis ?

Spring Framework は、主要なフルスタック Java/JEE アプリケーションフレームワークです。依存性注入、AOP、ポータブルサービスの抽象化を使用することで、軽量のコンテナーと非侵襲的なプログラミングモデルを提供します。

NoSQL (英語) ストレージシステムは、水平方向のスケーラビリティと速度に関して、従来の RDBMS に代わるものを提供します。実装に関しては、Key-Value ストアは NoSQL スペースで最大の(そして最も古い)メンバーの 1 つを表します。

Spring Data Redis(SDR)フレームワークは、Spring の優れたインフラストラクチャサポートを通じてストアとの対話に必要な冗長タスクと定型コードを排除することにより、Redis キー値ストアを使用する Spring アプリケーションを簡単に作成できるようにします。

10. Redis サポート

Spring Data でサポートされている Key-Value ストアの 1 つは Redis (英語) です。Redis プロジェクトのホームページを引用するには:

Redis は、高度な Key-Value ストアです。memcached に似ていますが、データセットは揮発性ではなく、値は memcached とまったく同じように文字列にすることができますが、リスト、セット、順序付きセットも使用できます。このすべてのデータ型は、要素のプッシュ / ポップ、要素の追加 / 削除、サーバー側の和集合、共通部分、セット間の差異などを実行するためのアトミック操作で操作できます。Redis は、さまざまな種類の並べ替え機能をサポートしています。

Spring Data Redis は、Spring アプリケーションから Redis への簡単な構成とアクセスを提供します。これは、ストアと対話するための低レベルと高レベルの両方の抽象化を提供し、ユーザーをインフラストラクチャの懸念から解放します。

10.1. 入門

作業環境をセットアップする簡単な方法は、Eclipse Pleiades All in One (STS, Lombok 付属) または STS (英語) で Spring ベースのプロジェクトを作成することです。

まず、実行中の Redis サーバーをセットアップする必要があります。

STS で Spring プロジェクトを作成するには:

  1. ファイル→新規→ Spring テンプレートプロジェクト→シンプル Spring ユーティリティプロジェクトに移動し、プロンプトが表示されたらはいを押します。次に、プロジェクトと org.spring.redis.example などのパッケージ名を入力します。.pom.xml ファイルの dependencies 要素に以下を追加します。

    <dependencies>
    
      <!-- other dependency elements omitted -->
    
      <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.5.0-M2</version>
      </dependency>
    
    </dependencies>
  2. pom.xml の Spring のバージョンを次のように変更する

    <spring.framework.version>5.3.3</spring.framework.version>
  3. 次の Maven の Spring マイルストーンリポジトリの場所を pom.xml に追加して、<dependencies/> 要素と同じレベルになるようにします。

    <repositories>
      <repository>
        <id>spring-milestone</id>
        <name>Spring Maven MILESTONE Repository</name>
        <url>https://repo.spring.io/libs-milestone</url>
      </repository>
    </repositories>

リポジトリも参照できます (英語)

10.2. Redis の要件

Spring Redis には Redis 2.6 以上が必要であり、Spring Data Redis は、Redis 用の 2 つの一般的なオープンソース Java ライブラリである Lettuce: GitHub (英語) および Jedis: GitHub (英語) と統合されます。

10.3. Redis は高レベルのビューをサポートする

Redis サポートは、いくつかのコンポーネントを提供します。ほとんどのタスクでは、高レベルの抽象化とサポートサービスが最良の選択です。いつでも、レイヤー間を移動できることに注意してください。例:Redis と直接通信するために、低レベルの接続(またはネイティブライブラリ)を取得できます。

10.4. Redis への接続

Redis および Spring を使用する場合の最初のタスクの 1 つは、IoC コンテナーを介してストアに接続することです。これを行うには、Java コネクター(またはバインディング)が必要です。選択したライブラリに関係なく、Spring Data Redis API の 1 つのセット(すべてのコネクターで一貫して動作する)のみを使用する必要があります。org.springframework.data.redis.connection パッケージと、Redis へのアクティブな接続を操作および取得するための RedisConnection および RedisConnectionFactory インターフェースです。

10.4.1. RedisConnection および RedisConnectionFactory

RedisConnection は、Redis バックエンドとの通信を処理するため、Redis 通信のコアビルドブロックを提供します。また、基になる接続ライブラリの例外を Spring の一貫した DAO 例外階層に自動的に変換するため、操作のセマンティクスは同じままであるため、コードを変更せずにコネクターを切り替えることができます。

ネイティブライブラリ API が必要なコーナーケースのために、RedisConnection は、通信に使用される生の基になるオブジェクトを返す専用のメソッド(getNativeConnection)を提供します。

アクティブな RedisConnection オブジェクトは、RedisConnectionFactory を介して作成されます。さらに、ファクトリは PersistenceExceptionTranslator オブジェクトとして機能します。つまり、宣言されると、透過的な例外変換を実行できます。例: @Repository アノテーションと AOP を使用して例外変換を行うことができます。詳細については、Spring Framework ドキュメントの専用セクションを参照してください。

基盤となる構成に応じて、ファクトリは新しい接続または既存の接続を返すことができます(プールまたは共有ネイティブ接続が使用されている場合)。

RedisConnectionFactory を操作する最も簡単な方法は、IoC コンテナーを介して適切なコネクターを構成し、それを using クラスに挿入することです。

残念ながら、現在、すべてのコネクターがすべての Redis 機能をサポートしているわけではありません。基盤となるライブラリでサポートされていない ConnectionAPI のメソッドを呼び出すと、UnsupportedOperationException がスローされます。次の概要では、個々の Redis コネクターでサポートされている機能について説明します。

表 1: Redis コネクター全体での機能の可用性
サポートされている機能 LettuceJedis

スタンドアロン接続

X

X

マスター / レプリカ接続

X

Redis Sentinel

マスタールックアップ、Sentinel 認証、レプリカ読み取り

マスタールックアップ

Redis クラスター

クラスター接続、クラスターノード接続、レプリカ読み取り

クラスター接続、クラスターノード接続

輸送チャネル

TCP、OS ネイティブ TCP(epoll、kqueue)、Unix ドメインソケット

TCP

接続プーリング

X ( commons-pool2 を使用)

X ( commons-pool2 を使用)

その他の接続機能

ノンブロッキングコマンドのシングルトン接続の共有

JedisShardInfo サポート

SSL サポート

X

X

Pub/Sub

X

X

Pipelining

X

X

Transactions

X

X

データ型のサポート

キー、文字列、リスト、セット、並べ替えられたセット、ハッシュ、サーバー、ストリーム、スクリプト、地理、HyperLogLog

キー、文字列、リスト、セット、並べ替えられたセット、ハッシュ、サーバー、スクリプト、地理、HyperLogLog

リアクティブ(ノンブロッキング)API

X

10.4.2. Lettuce コネクターの構成

Lettuce: GitHub (英語) は、org.springframework.data.redis.connection.lettuce パッケージを介して Spring Data Redis によってサポートされる Netty (英語) ベースのオープンソースコネクターです。

以下を pom.xml ファイルの dependencies 要素に追加します。
<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.0.2.RELEASE</version>
  </dependency>

</dependencies>

次の例は、新しい Lettuce 接続ファクトリを作成する方法を示しています。

@Configuration
class AppConfig {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
  }
}

微調整できるレタス固有の接続パラメーターもいくつかあります。デフォルトでは、LettuceConnectionFactory によって作成されたすべての LettuceConnection インスタンスは、すべてのノンブロッキングおよび非トランザクション操作に対して同じスレッドセーフなネイティブ接続を共有します。毎回専用接続を使用する場合は、shareNativeConnection を false に設定してください。LettuceConnectionFactory は、shareNativeConnection が false に設定されている場合、ブロッキングおよびトランザクション接続またはすべての接続をプールするために LettucePool を使用するように構成することもできます。

Lettuce は Netty のネイティブトランスポート (英語) と統合されているため、Unix ドメインソケットを使用して Redis と通信できます。ランタイム環境に一致する適切なネイティブトランスポートの依存関係を含めるようにしてください。次の例は、/var/run/redis.sock で Unix ドメインソケット用の Lettuce 接続ファクトリを作成する方法を示しています。

@Configuration
class AppConfig {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
  }
}
Netty は現在、OS ネイティブトランスポート用の epoll(Linux)および kqueue(BSD/macOS)インターフェースをサポートしています。

10.4.3. Jedis コネクターの構成

Jedis: GitHub (英語) は、org.springframework.data.redis.connection.jedis パッケージを介して Spring Data Redis モジュールによってサポートされるコミュニティ駆動型コネクターです。

以下を pom.xml ファイルの dependencies 要素に追加します。
<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
  </dependency>

</dependencies>

最も単純な形式では、Jedis の構成は次のようになります。

@Configuration
class AppConfig {

  @Bean
  public JedisConnectionFactory redisConnectionFactory() {
    return new JedisConnectionFactory();
  }
}

ただし、本番環境で使用する場合は、次の例に示すように、ホストやパスワードなどの設定を微調整することをお勧めします。

@Configuration
class RedisConfiguration {

  @Bean
  public JedisConnectionFactory redisConnectionFactory() {

    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379);
    return new JedisConnectionFactory(config);
  }
}

10.4.4. マスターへの書き込み、レプリカからの読み取り

Redis マスター / レプリカのセットアップ  —  自動フェイルオーバーなし (自動フェイルオーバーについては、を参照してください: Sentinel ) —  より多くのノードにデータを安全に保存できるだけではありません。また、Lettuce を使用して、マスターへの書き込みをプッシュしながらレプリカからデータを読み取ることもできます。次の例に示すように、LettuceClientConfiguration を使用して、使用する読み取り / 書き込み戦略を設定できます。

@Configuration
class WriteToMasterReadFromReplicaConfiguration {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
      .readFrom(SLAVE_PREFERRED)
      .build();

    RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);

    return new LettuceConnectionFactory(serverConfig, clientConfig);
  }
}
INFO コマンドを介して非公開アドレスを報告する環境の場合(たとえば、AWS を使用する場合)、RedisStandaloneConfiguration の代わりに RedisStaticMasterReplicaConfiguration を使用します。個々のサーバー間での Pub/Sub メッセージの伝播が欠落しているため、RedisStaticMasterReplicaConfiguration は Pub/Sub をサポートしていないことに注意してください。

10.5. Redis Sentinel サポート

次の例に示すように、高可用性 Redis を処理するために、Spring Data Redis は RedisSentinelConfiguration を使用して Redis Sentinel (英語) をサポートしています。

/**
 * Jedis
 */
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
  .master("mymaster")
  .sentinel("127.0.0.1", 26379)
  .sentinel("127.0.0.1", 26380);
  return new JedisConnectionFactory(sentinelConfig);
}

/**
 * Lettuce
 */
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
  .master("mymaster")
  .sentinel("127.0.0.1", 26379)
  .sentinel("127.0.0.1", 26380);
  return new LettuceConnectionFactory(sentinelConfig);
}

RedisSentinelConfiguration は PropertySource で定義することもできます。これにより、次のプロパティを設定できます。

プロパティの構成
  • spring.redis.sentinel.master: マスターノードの名前。

  • spring.redis.sentinel.nodes: ホスト:ポートのペアのコンマ区切りリスト。

  • spring.redis.sentinel.password: Redis で認証するときに適用するパスワード Sentinel

場合によっては、センチネルの 1 つと直接対話する必要があります。RedisConnectionFactory.getSentinelConnection() または RedisConnection.getSentinelCommands() を使用すると、最初に構成されたアクティブな Sentinel にアクセスできます。

10.6. RedisTemplate を介したオブジェクトの操作

ほとんどのユーザーは、RedisTemplate とそれに対応するパッケージ org.springframework.data.redis.core を使用する可能性があります。テンプレートは、その豊富な機能セットにより、実際、Redis モジュールの中心的なクラスです。テンプレートは、Redis インタラクションの高レベルの抽象化を提供します。RedisConnection は、バイナリ値(byte 配列)を受け入れて返す低レベルのメソッドを提供しますが、テンプレートは直列化と接続管理を処理し、ユーザーがそのような詳細を処理する必要がなくなります。

さらに、テンプレートは、次の表で説明するように、特定のタイプまたは特定のキー(KeyBound インターフェースを介して)に対して作業するための豊富な汎用インターフェースを提供する操作ビュー(Redis コマンドリファレンス (英語) からのグループ化に続く)を提供します。

テーブル 2: 運用ビュー
インターフェース 説明

キータイプの操作

GeoOperations

GEOADDGEORADIUS などの Redis 地理空間操作、…

HashOperations

Redis ハッシュ演算

HyperLogLogOperations

Redis PFADDPFCOUNT などの HyperLogLog 操作、…

ListOperations

Redis リスト操作

SetOperations

Redis セット操作

ValueOperations

Redis 文字列(または値)操作

ZSetOperations

Redis zset(またはソートされたセット)操作

キーバウンドオペレーション

BoundGeoOperations

Redis キーバウンド地理空間操作

BoundHashOperations

Redis ハッシュキーバインド操作

BoundKeyOperations

Redis キーバインド操作

BoundListOperations

Redis リストキーバインド操作

BoundSetOperations

Redis はキーバウンド操作を設定する

BoundValueOperations

Redis 文字列(または値)キーバインド操作

BoundZSetOperations

Redis zset(またはソートされたセット)キーバインド操作

構成が完了すると、テンプレートはスレッドセーフになり、複数のインスタンスで再利用できます。

RedisTemplate は、ほとんどの操作に Java ベースのシリアライザーを使用します。これは、テンプレートによって書き込まれた、または読み取られたオブジェクトはすべて、Java を介して直列化および逆直列化されることを意味します。テンプレートの直列化メカニズムを変更できます。Redis モジュールは、org.springframework.data.redis.serializer パッケージで利用可能ないくつかの実装を提供します。詳細については、シリアライザーを参照してください。enableDefaultSerializer プロパティを false に設定することにより、任意のシリアライザーを null に設定し、生のバイト配列で RedisTemplate を使用することもできます。テンプレートでは、すべてのキーが null 以外である必要があることに注意してください。ただし、基礎となるシリアライザーが値を受け入れる限り、値は null にすることができます。詳細については、各シリアライザーの Javadoc を参照してください。

特定のテンプレートビューが必要な場合は、ビューを依存関係として宣言し、テンプレートを挿入します。次の例に示すように、コンテナーは自動的に変換を実行し、opsFor[X] 呼び出しを排除します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
  <!-- redis template definition -->
  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
  ...

</beans>
public class Example {

  // inject the actual template
  @Autowired
  private RedisTemplate<String, String> template;

  // inject the template as ListOperations
  @Resource(name="redisTemplate")
  private ListOperations<String, String> listOps;

  public void addLink(String userId, URL url) {
    listOps.leftPush(userId, url.toExternalForm());
  }
}

10.7. 文字列に焦点を当てたコンビニエンスクラス

Redis に格納されているキーと値が java.lang.String であることが非常に一般的であるため、Redis モジュールは、RedisConnection と RedisTemplate に、それぞれ StringRedisConnection (およびその DefaultStringRedisConnection 実装)と StringRedisTemplate の 2 つの拡張機能を提供し、集中的な文字列操作の便利なワンストップソリューションとして提供します。テンプレートと接続は、String キーにバインドされるだけでなく、下にある StringRedisSerializer を使用します。つまり、保存されているキーと値は人間が読める形式です(Redis とコードの両方で同じエンコーディングが使用されていると仮定します)。次のリストは例を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>

  <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
  ...
</beans>
public class Example {

  @Autowired
  private StringRedisTemplate redisTemplate;

  public void addLink(String userId, URL url) {
    redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
  }
}

他の Spring テンプレートと同様に、RedisTemplate および StringRedisTemplate を使用すると、RedisCallback インターフェースを介して Redis と直接通信できます。この機能は、RedisConnection と直接通信するため、完全に制御できます。StringRedisTemplate が使用されている場合、コールバックは StringRedisConnection のインスタンスを受け取ることに注意してください。次の例は、RedisCallback インターフェースの使用方法を示しています。

public void useCallback() {

  redisTemplate.execute(new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
      Long size = connection.dbSize();
      // Can cast to StringRedisConnection if using a StringRedisTemplate
      ((StringRedisConnection)connection).set("key", "value");
    }
   });
}

10.8. シリアライザー

フレームワークの観点から、Redis に格納されるデータはバイトのみです。Redis 自体はさまざまなタイプをサポートしていますが、ほとんどの場合、これらはデータが表すものではなく、データの格納方法を指します。情報を文字列に変換するか、その他のオブジェクトに変換するかは、ユーザーが決定します。

Spring Data では、ユーザー(カスタム)タイプと生データ(およびその逆)の間の変換は、org.springframework.data.redis.serializer パッケージの Redis で処理されます。

このパッケージには、名前が示すように、直列化プロセスを処理する 2 種類のシリアライザーが含まれています。

  • RedisSerializer に基づく双方向シリアライザー。

  • RedisElementReader および RedisElementWriter を使用する要素リーダーおよびライター。

これらのバリアントの主な違いは、RedisSerializer は主に byte[] に直列化されるのに対し、リーダーとライターは ByteBuffer を使用することです。

複数の実装が利用可能です(このドキュメントですでにメンションされている 2 つを含む):

  • JdkSerializationRedisSerializer。これは、RedisCache および RedisTemplate にデフォルトで使用されます。

  • StringRedisSerializer

ただし、Spring OXM サポートを介したオブジェクト / XML マッピングに OxmSerializer を使用することも、JSON (英語) 形式でデータを格納するために Jackson2JsonRedisSerializer または GenericJackson2JsonRedisSerializer を使用することもできます。

ストレージ形式は値だけに限定されないことに注意してください。キー、値、またはハッシュに制限なく使用できます。

デフォルトでは、RedisCache および RedisTemplate は Java ネイティブ直列化を使用するように構成されています。Java ネイティブ直列化は、脆弱なライブラリやクラスを悪用して未検証のバイトコードを挿入するペイロードによって引き起こされるリモートコードの実行を許可することで知られています。入力を操作すると、逆直列化ステップ中にアプリケーションで不要なコードが実行される可能性があります。結果として、信頼できない環境では直列化を使用しないでください。一般に、代わりに他のメッセージ形式(JSON など)を強くお勧めします。

Java 直列化によるセキュリティの脆弱性が懸念される場合は、コア JVM レベルの汎用直列化フィルターメカニズムを検討してください。元々は JDK 9 用に開発されますが、JDK8, 7 にバックポートされています。

10.9. ハッシュマッピング

データは、Redis 内のさまざまなデータ構造を使用して保存できます。Jackson2JsonRedisSerializer は、オブジェクトを JSON (英語) 形式に変換できます。理想的には、JSON はプレーンキーを使用して値として保存できます。Redis ハッシュを使用すると、構造化オブジェクトのより高度なマッピングを実現できます。Spring Data Redis は、データをハッシュにマッピングするためのさまざまな戦略を提供します(ユースケースによって異なります)。

10.9.1. ハッシュマッパー

ハッシュマッパーは、マップオブジェクトを Map<K, V> に変換したり戻したりするものです。HashMapper は、Redis ハッシュでの使用を目的としています。

複数の実装が利用可能です:

次の例は、ハッシュマッピングを実装する 1 つの方法を示しています。

public class Person {
  String firstname;
  String lastname;

  //  …
}

public class HashMapping {

  @Autowired
  HashOperations<String, byte[], byte[]> hashOperations;

  HashMapper<Object, byte[], byte[]> mapper = new ObjectHashMapper();

  public void writeHash(String key, Person person) {

    Map<byte[], byte[]> mappedHash = mapper.toHash(person);
    hashOperations.putAll(key, mappedHash);
  }

  public Person loadHash(String key) {

    Map<byte[], byte[]> loadedHash = hashOperations.entries("key");
    return (Person) mapper.fromHash(loadedHash);
  }
}

10.9.2. Jackson2HashMapper

Jackson2HashMapper は、FasterXML Jackson: GitHub (英語) を使用して、ドメインオブジェクトの Redis ハッシュマッピングを提供します。Jackson2HashMapper は、トップレベルのプロパティをハッシュフィールド名としてマップし、オプションで構造をフラット化できます。単純な型は単純な値にマップされます。複雑なタイプ(ネストされたオブジェクト、コレクション、マップなど)は、ネストされた JSON として表されます。

Flattening は、ネストされたすべてのプロパティに対して個別のハッシュエントリを作成し、可能な限り複雑な型を単純な型に解決します。

次のクラスとそれに含まれるデータ構造について考えてみます。

public class Person {
  String firstname;
  String lastname;
  Address address;
  Date date;
  LocalDateTime localDateTime;
}

public class Address {
  String city;
  String country;
}

次の表は、前のクラスのデータが法線マッピングでどのように表示されるかを示しています。

表 3: 通常マッピング
ハッシュフィールド

firstname

Jon

lastname

Snow

address

{ "city" : "Castle Black", "country" : "The North" }

date

1561543964015

localDateTime

2018-01-02T12:13:14

次の表は、前のクラスのデータがフラットマッピングでどのように表示されるかを示しています。

表 4: フラットマッピング
ハッシュフィールド

firstname

Jon

lastname

Snow

address.city

Castle Black

address.country

The North

date

1561543964015

localDateTime

2018-01-02T12:13:14

フラット化では、すべてのプロパティ名が JSON パスに干渉しないようにする必要があります。フラット化を使用する場合、マップキーまたはプロパティ名としてドットまたは括弧を使用することはサポートされていません。結果のハッシュをオブジェクトにマップして戻すことはできません。
java.util.Date と java.util.Calendar はミリ秒で表されます。JSR-310 日付 / 時刻タイプは、jackson-datatype-jsr310 がクラスパス上にある場合、toString 形式に直列化されます。

10.10. Redis メッセージング (Pub/Sub)

Spring Data は、Spring Framework の JMS 統合と機能および命名が類似した、Redis 専用のメッセージング統合を提供します。

Redis メッセージングは、機能の 2 つの領域に大きく分けることができます。

  • メッセージの公開または作成

  • メッセージのサブスクリプションまたは消費

これは、パブリッシュ / サブスクライブ(略して Pub / サブ)と呼ばれることが多いパターンの例です。RedisTemplate クラスは、メッセージの生成に使用されます。Java EE のメッセージ駆動型 Bean スタイルと同様の非同期受信の場合、Spring Data は、メッセージ駆動型 POJO(MDP)の作成に使用される専用のメッセージリスナーコンテナーを提供し、同期受信の場合は RedisConnection 契約を提供します。

org.springframework.data.redis.connection および org.springframework.data.redis.listener パッケージは、Redis メッセージングのコア機能を提供します。

10.10.1. 公開 (メッセージ送信)

メッセージを公開するには、他の操作と同様に、低レベルの RedisConnection または高レベルの RedisTemplate のいずれかを使用できます。どちらのエンティティも、メッセージと宛先チャネルを引数として受け入れる publish メソッドを提供します。RedisConnection には生データ(バイトの配列)が必要ですが、RedisTemplate では、次の例に示すように、任意のオブジェクトをメッセージとして渡すことができます。

// send message through connection RedisConnection con = ...
byte[] msg = ...
byte[] channel = ...
con.publish(msg, channel); // send message through RedisTemplate
RedisTemplate template = ...
template.convertAndSend("hello!", "world");

10.10.2. 購読する (メッセージの受信)

受信側では、チャネルに直接名前を付けるか、パターンマッチングを使用して、1 つまたは複数のチャネルにサブスクライブできます。後者のアプローチは、1 つのコマンドで複数のサブスクリプションを作成できるだけでなく、サブスクリプション時にまだ作成されていないチャネルをリッスンできるため(パターンに一致する限り)非常に便利です。

低レベルでは、RedisConnection は、チャネルまたはパターンでそれぞれサブスクライブするために Redis コマンドをマップする subscribe メソッドと pSubscribe メソッドを提供します。複数のチャネルまたはパターンを引数として使用できることに注意してください。接続のサブスクリプションを変更したり、接続がリッスンしているかどうかを照会したりするために、RedisConnection は getSubscription メソッドと isSubscribed メソッドを提供します。

Spring Data のサブスクリプションコマンド Redis がブロックしています。つまり、接続で subscribe を呼び出すと、現在のスレッドがメッセージの待機を開始するときにブロックされます。スレッドは、サブスクリプションがキャンセルされた場合にのみ解放されます。これは、別のスレッドが同じ接続で unsubscribe または pUnsubscribe を呼び出したときに発生します。この問題の解決策については、「メッセージリスナコンテナー」(このドキュメントの後半)を参照してください。

前述のように、サブスクライブすると、接続はメッセージの待機を開始します。新しいサブスクリプションの追加、既存のサブスクリプションの変更、および既存のサブスクリプションのキャンセルを行うコマンドのみが許可されます。subscribepSubscribeunsubscribe または pUnsubscribe 以外のものを呼び出すと、例外がスローされます。

メッセージをサブスクライブするには、MessageListener コールバックを実装する必要があります。新しいメッセージが到着するたびに、コールバックが呼び出され、ユーザーコードが onMessage メソッドによって実行されます。インターフェースは、実際のメッセージだけでなく、メッセージが受信されたチャネル、およびチャネルと一致するためにサブスクリプションによって使用されるパターン(存在する場合)へのアクセスを提供します。この情報により、受信者はコンテンツだけでなく、追加の詳細を調べることによって、さまざまなメッセージを区別できます。

メッセージリスナコンテナー

低レベルのサブスクリプションはブロッキングの性質があるため、すべてのリスナーに接続とスレッドの管理が必要になるため、魅力的ではありません。この問題を軽減するために、Spring Data は RedisMessageListenerContainer を提供します。これは、すべての面倒な作業を行います。EJB と JMS に精通している場合は、Spring Framework とそのメッセージ駆動型 POJO(MDP)のサポートに可能な限り近いように設計されているため、概念に精通している必要があります。

RedisMessageListenerContainer は、メッセージリスナーコンテナーとして機能します。これは、Redis チャネルからメッセージを受信し、それに注入される MessageListener インスタンスを駆動するために使用されます。リスナーコンテナーは、メッセージ受信のすべてのスレッド化を担当し、処理のためにリスナーにディスパッチします。メッセージリスナーコンテナーは、MDP とメッセージングプロバイダー間の仲介者であり、メッセージを受信するための登録、リソースの取得と解放、例外変換などを処理します。これにより、アプリケーション開発者は、メッセージの受信(およびメッセージへの応答)に関連する(おそらく複雑な)ビジネスロジックを記述し、定型的な Redis インフラストラクチャの懸念事項をフレームワークに委譲できます。

さらに、アプリケーションのフットプリントを最小限に抑えるために、RedisMessageListenerContainer では、サブスクリプションを共有していなくても、1 つの接続と 1 つのスレッドを複数のリスナーで共有できます。アプリケーションが追跡するリスナーまたはチャネルの数に関係なく、ランタイムコストはその存続期間を通じて同じままです。さらに、コンテナーではランタイム構成の変更が可能であるため、アプリケーションの実行中にリスナーを追加または削除でき、再起動する必要はありません。さらに、コンテナーは、必要な場合にのみ RedisConnection を使用する、遅延サブスクリプションアプローチを使用します。すべてのリスナーがサブスクライブ解除されると、クリーンアップが自動的に実行され、スレッドが解放されます。

メッセージの非同期性を支援するために、コンテナーにはメッセージをディスパッチするための java.util.concurrent.Executor (または Spring の TaskExecutor)が必要です。負荷、リスナーの数、またはランタイム環境に応じて、ニーズにより適切に対応するようにエグゼキュータを変更または微調整する必要があります。特に、管理された環境(アプリサーバーなど)では、ランタイムを利用するために適切な TaskExecutor を選択することを強くお勧めします。

MessageListenerAdapter

MessageListenerAdapter クラスは、Spring の非同期メッセージングサポートの最後のコンポーネントです。一言で言えば、ほとんどすべてのクラスを MDP として公開できます(ただし、いくつかの制約があります)。

次のインターフェース定義を検討してください。

public interface MessageDelegate {
  void handleMessage(String message);
  void handleMessage(Map message); void handleMessage(byte[] message);
  void handleMessage(Serializable message);
  // pass the channel/pattern as well
  void handleMessage(Serializable message, String channel);
 }

このインターフェースは MessageListener インターフェースを継承していませんが、MessageListenerAdapter クラスを使用することで MDP として使用できることに注意してください。また、受信および処理できるさまざまな Message タイプの内容に応じて、さまざまなメッセージ処理メソッドがどのように強く型付けされているかに注意してください。さらに、メッセージの送信先のチャネルまたはパターンは、タイプ String の 2 番目の引数としてメソッドに渡すことができます。

public class DefaultMessageDelegate implements MessageDelegate {
  // implementation elided for clarity...
}

上記の MessageDelegate インターフェースの実装(上記の DefaultMessageDelegate クラス)には、 Redis の依存関係がまったくないことに注意してください。これは、次の構成で MDP にする POJO です。

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:redis="http://www.springframework.org/schema/redis"
   xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/redis https://www.springframework.org/schema/redis/spring-redis.xsd">

<!-- the default ConnectionFactory -->
<redis:listener-container>
  <!-- the method attribute can be skipped as the default method name is "handleMessage" -->
  <redis:listener ref="listener" method="handleMessage" topic="chatroom" />
</redis:listener-container>

<bean id="listener" class="redisexample.DefaultMessageDelegate"/>
 ...
<beans>
リスナートピックは、チャネル(topic="chatroom" など)またはパターンのいずれかです。(たとえば、topic="*room")

上記の例では、Redis 名前空間を使用して、メッセージリスナーコンテナーを宣言し、POJO をリスナーとして自動的に登録します。本格的な Bean の定義は次のとおりです。

<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
  <constructor-arg>
    <bean class="redisexample.DefaultMessageDelegate"/>
  </constructor-arg>
</bean>

<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
  <property name="connectionFactory" ref="connectionFactory"/>
  <property name="messageListeners">
    <map>
      <entry key-ref="messageListener">
        <bean class="org.springframework.data.redis.listener.ChannelTopic">
          <constructor-arg value="chatroom"/>
        </bean>
      </entry>
    </map>
  </property>
</bean>

メッセージが受信されるたびに、アダプターは、低レベルのフォーマットと必要なオブジェクトタイプの間で(構成された RedisSerializer を使用して)自動的かつ透過的に変換を実行します。メソッドの呼び出しによって発生した例外はすべて、コンテナーによってキャッチおよび処理されます(デフォルトでは、例外はログに記録されます)。

10.11. Redis ストリーム

Redis Streams は、抽象的なアプローチでログデータ構造をモデル化します。通常、ログは追加専用のデータ構造であり、最初から、ランダムな位置で、または新しいメッセージをストリーミングすることによって消費されます。

Redis リファレンスドキュメント (英語) の Redis ストリームの詳細を参照してください。

Redis ストリームは、大きく 2 つの機能領域に分けることができます。

  • レコードの追加

  • レコードの消費

このパターンは Pub/Sub と類似していますが、主な違いはメッセージの永続性とメッセージの消費メソッドにあります。

Pub/Sub は一時的なメッセージのブロードキャストに依存していますが(つまり、聞いていない場合はメッセージを見逃します)、Redis ストリームは、ストリームがトリミングされるまでメッセージを保持する永続的な追加専用データ型を使用します。消費のもう 1 つの違いは、Pub/Sub がサーバー側のサブスクリプションを登録することです。Redis は到着メッセージをクライアントにプッシュしますが、Redis ストリームはアクティブなポーリングを必要とします。

org.springframework.data.redis.connection および org.springframework.data.redis.stream パッケージは、Redis ストリームのコア機能を提供します。

Redis ストリームのサポートは、Jedis でまだサポートされていないため、現在 Lettuce クライアントでのみ利用できます。

10.11.1. 追加

レコードを送信するには、他の操作と同様に、低レベルの RedisConnection または高レベルの StreamOperations のいずれかを使用できます。どちらのエンティティも、レコードと宛先ストリームを引数として受け入れる add (xAdd)メソッドを提供します。RedisConnection には生データ(バイトの配列)が必要ですが、StreamOperations では、次の例に示すように、任意のオブジェクトをレコードとして渡すことができます。

// append message through connection
RedisConnection con =  …
byte[] stream =  …
ByteRecord record = StreamRecords.rawBytes(…).withStreamKey(stream);
con.xAdd(record);

// append message through RedisTemplate
RedisTemplate template =  …
StringRecord record = StreamRecords.string(…).withStreamKey("my-stream");
template.streamOps().add(record);

ストリームレコードは、ペイロードとして Map、Key-Value タプルを運びます。レコードをストリームに追加すると、さらなる参照として使用できる RecordId が返されます。

10.11.2. 消費する

消費側では、1 つまたは複数のストリームを消費できます。Redis ストリームは、既知のストリームコンテンツ内およびストリームの終わりを超えて任意の位置(ランダムアクセス)からストリームを消費して新しいストリームレコードを消費できるようにする読み取りコマンドを提供します。

低レベルでは、RedisConnection は、Redis コマンドをそれぞれコンシューマーグループ内で読み取るためにマップする xRead メソッドと xReadGroup メソッドを提供します。複数のストリームを引数として使用できることに注意してください。

Redis のサブスクリプションコマンドがブロックしている可能性があります。つまり、接続で xRead を呼び出すと、現在のスレッドがメッセージの待機を開始するときにブロックされます。スレッドは、読み取りコマンドがタイムアウトするか、メッセージを受信した場合にのみ解放されます。

ストリームメッセージを消費するには、アプリケーションコード内のメッセージをポーリングするか、2 つのメッセージリスナーコンテナーを介した非同期受信のいずれか(命令型またはリアクティブ型)を使用できます。新しいレコードが到着するたびに、コンテナーはアプリケーションコードを通知します。

同期受信

ストリームの消費は通常、非同期処理に関連付けられていますが、メッセージを同期的に消費することもできます。オーバーロードされた StreamOperations.read(…) メソッドは、この機能を提供します。同期受信中、呼び出し元のスレッドは、メッセージが使用可能になるまでブロックする可能性があります。プロパティ StreamReadOptions.block は、受信者がメッセージの待機をあきらめる前に待機する時間を指定します。

// Read message through RedisTemplate
RedisTemplate template =  …

List<MapRecord<K, HK, HV>> messages = template.streamOps().read(StreamReadOptions.empty().count(2),
				StreamOffset.latest("my-stream"));

List<MapRecord<K, HK, HV>> messages = template.streamOps().read(Consumer.from("my-group", "my-consumer"),
				StreamReadOptions.empty().count(2),
				StreamOffset.create("my-stream", ReadOffset.lastConsumed()))
メッセージリスナーコンテナーを介した非同期受信

低レベルのポーリングはブロッキングの性質があるため、すべてのコンシューマーに対して接続とスレッドの管理が必要になるため、魅力的ではありません。この問題を軽減するために、Spring Data はメッセージリスナーを提供します。メッセージリスナーはすべての面倒な作業を行います。EJB と JMS に精通している場合は、Spring Framework とそのメッセージ駆動型 POJO(MDP)のサポートに可能な限り近いように設計されているため、概念に精通している必要があります。

Spring Data には、使用されるプログラミングモデルに合わせて調整された 2 つの実装が付属しています。

  • StreamMessageListenerContainer は、命令型プログラミングモデルのメッセージリスナーコンテナーとして機能します。これは、Redis ストリームからのレコードを消費し、それに注入される StreamListener インスタンスを駆動するために使用されます。

  • StreamReceiver は、メッセージリスナーのリアクティブバリアントを提供します。これは、Redis ストリームからのメッセージを潜在的に無限のストリームとして消費し、Flux を介してストリームメッセージを送信するために使用されます。

StreamMessageListenerContainer と StreamReceiver は、メッセージ受信のすべてのスレッド化と、処理のためのリスナーへのディスパッチを担当します。メッセージリスナーコンテナー / レシーバーは、MDP とメッセージングプロバイダー間の仲介者であり、メッセージを受信するための登録、リソースの取得と解放、例外変換などを処理します。これにより、アプリケーション開発者は、メッセージの受信(およびメッセージへの応答)に関連する(おそらく複雑な)ビジネスロジックを記述し、定型的な Redis インフラストラクチャの懸念事項をフレームワークに委譲できます。

どちらのコンテナーでもランタイム構成の変更が可能であるため、アプリケーションの実行中に再起動せずにサブスクリプションを追加または削除できます。さらに、コンテナーは、必要な場合にのみ RedisConnection を使用する、遅延サブスクリプションアプローチを使用します。すべてのリスナーがサブスクライブ解除されると、自動的にクリーンアップが実行され、スレッドが解放されます。

命令型 StreamMessageListenerContainer

EJB の世界のメッセージ駆動型 Bean(MDB)と同様に、ストリーム駆動型 POJO(SDP)はストリームメッセージの受信者として機能します。SDP の 1 つの制限は、org.springframework.data.redis.stream.StreamListener インターフェースを実装する必要があることです。POJO が複数のスレッドでメッセージを受信する場合は、実装がスレッドセーフであることを確認することが重要であることにも注意してください。

class ExampleStreamListener implements StreamListener<String, MapRecord<String, String, String>> {

	@Override
	public void onMessage(MapRecord<String, String, String> message) {

		System.out.println("MessageId: " + message.getId());
		System.out.println("Stream: " + message.getStream());
		System.out.println("Body: " + message.getValue());
	}
}

StreamListener は関数インターフェースを表すため、Lambda 形式を使用して実装を書き直すことができます。

message -> {

    System.out.println("MessageId: " + message.getId());
    System.out.println("Stream: " + message.getStream());
    System.out.println("Body: " + message.getValue());
};

StreamListener を実装したら、メッセージリスナーコンテナーを作成してサブスクリプションを登録します。

RedisConnectionFactory connectionFactory =  …
StreamListener<String, MapRecord<String, String, String>> streamListener =  …

StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> containerOptions = StreamMessageListenerContainerOptions
			.builder().pollTimeout(Duration.ofMillis(100)).build();

StreamMessageListenerContainer<String, MapRecord<String, String, String>> container = StreamMessageListenerContainer.create(connectionFactory,
				containerOptions);

Subscription subscription = container.receive(StreamOffset.fromStart("my-stream"), streamListener);

各実装でサポートされている機能の詳細については、さまざまなメッセージリスナーコンテナーの Javadoc を参照してください。

反応性 StreamReceiver

ストリーミングデータソースのリアクティブな消費は、通常、イベントまたはメッセージの Flux を介して発生します。リアクティブレシーバーの実装には、StreamReceiver とそのオーバーロードされた receive(…) メッセージが付属しています。リアクティブアプローチは、ドライバーによって提供されるスレッドリソースを活用しているため、StreamMessageListenerContainer と比較して、スレッドなどのインフラストラクチャリソースが少なくて済みます。受信ストリームは、StreamMessage の需要主導型パブリッシャーです。

Flux<MapRecord<String, String, String>> messages =  …

return messages.doOnNext(it -> {
    System.out.println("MessageId: " + message.getId());
    System.out.println("Stream: " + message.getStream());
    System.out.println("Body: " + message.getValue());
});

次に、StreamReceiver を作成し、サブスクリプションを登録してストリームメッセージを消費する必要があります。

ReactiveRedisConnectionFactory connectionFactory =  …

StreamReceiverOptions<String, MapRecord<String, String, String>> options = StreamReceiverOptions.builder().pollTimeout(Duration.ofMillis(100))
				.build();
StreamReceiver<String, MapRecord<String, String, String>> receiver = StreamReceiver.create(connectionFactory, options);

Flux<MapRecord<String, String, String>> messages = receiver.receive(StreamOffset.fromStart("my-stream"));

各実装でサポートされている機能の詳細については、さまざまなメッセージリスナーコンテナーの Javadoc を参照してください。

需要主導型の消費は、バックプレッシャー信号を使用してポーリングをアクティブ化および非アクティブ化します。StreamReceiver サブスクリプションは、サブスクライバーがさらに要求を通知するまで要求が満たされると、ポーリングを一時停止します。ReadOffset 戦略によっては、これによりメッセージがスキップされる可能性があります。
Acknowledge 戦略

Consumer Group を介してメッセージを読み取ると、サーバーは特定のメッセージが配信されたことを記憶し、それを保留中のエントリリスト(PEL)に追加します。配信されましたがまだ確認されていないメッセージのリスト。
以下のスニペットに示すように、保留中のエントリリストからメッセージを削除するには、StreamOperations.acknowledge を介してメッセージを確認する必要があります。

StreamMessageListenerContainer<String, MapRecord<String, String, String>> container = ...

container.receive(Consumer.from("my-group", "my-consumer"), (1)
	StreamOffset.create("my-stream", ReadOffset.lastConsumed()),
    msg -> {

	    // ...
	    redisTemplate.opsForStream().acknowledge("my-group", msg); (2)
    });
1 グループ my-group から my-consumer として読み取ります。受信したメッセージは確認されません。
2 処理後にメッセージを確認しました。
受信時にメッセージを自動確認するには、receive の代わりに receiveAutoAck を使用します。
ReadOffset 戦略

ストリーム読み取り操作は、読み取りオフセット指定を受け入れて、で指定されたオフセットからのメッセージを消費します。ReadOffset は、読み取りオフセット仕様を表します。Redis は、ストリームをスタンドアロンで消費するか、コンシューマーグループ内で消費するかに応じて、オフセットの 3 つのバリアントをサポートします。

  • ReadOffset.latest() –最新のメッセージを読みます。

  • ReadOffset.from(…) –特定のメッセージ ID の後に読み取ります。

  • ReadOffset.lastConsumed() –最後に消費されたメッセージ ID の後に読み取ります(コンシューマーグループのみ)。

メッセージコンテナーベースの消費のコンテキストでは、メッセージを消費するときに読み取りオフセットを進める(またはインクリメントする)必要があります。前進は、リクエストされた ReadOffset と消費モード(消費者グループあり / なし)によって異なります。次のマトリックスは、コンテナーが ReadOffset をどのように進めるかを説明しています。

表 5: ReadOffset 前進
オフセットの読み取り スタンドアロン 消費者グループ

最新

最新のメッセージを読む

最新のメッセージを読む

特定のメッセージ ID

最後に見たメッセージを次の MessageId として使用する

最後に見たメッセージを次の MessageId として使用する

最後に消費された

最後に見たメッセージを次の MessageId として使用する

消費者グループごとに最後に消費されたメッセージ

特定のメッセージ ID と最後に消費されたメッセージからの読み取りは、ストリームに追加されたすべてのメッセージの消費を保証する安全な操作と見なすことができます。最新のメッセージを読み取りに使用すると、ポーリング操作がデッドタイムの状態にあるときにストリームに追加されたメッセージをスキップできます。ポーリングにより、個々のポーリングコマンド間にメッセージが到着するデッドタイムが発生します。ストリームの消費は、線形の連続した読み取りではなく、繰り返しの XREAD 呼び出しに分割されます。

直列化

ストリームに送信されるすべてのレコードは、バイナリ形式に直列化する必要があります。ストリームがハッシュデータ構造に近いため、ストリームキー、フィールド名、値は、RedisTemplate で構成された対応するシリアライザーを使用します。

表 6: ストリームの直列化
ストリームプロパティ シリアライザー 説明

キー

keySerializer

Record#getStream() に使用

フィールド

hashKeySerializer

ペイロードの各マップキーに使用されます

value

hashValueSerializer

ペイロードの各マップ値に使用されます

使用中の RedisSerializer を確認し、シリアライザーを使用しない場合は、それらの値がすでにバイナリであることを確認する必要があることに注意してください。

オブジェクトマッピング
シンプル値

StreamOperations を使用すると、ObjectRecord を介して単純な値をストリームに直接追加できます。これらの値を Map 構造体に配置する必要はありません。次に、値はペイロードフィールドに割り当てられ、値を読み戻すときに抽出できます。

ObjectRecord<String, String> record = StreamRecords.newRecord()
    .in("my-stream")
    .ofObject("my-value");

redisTemplate()
    .opsForStream()
    .add(record); (1)

List<ObjectRecord<String, String>> records = redisTemplate()
    .opsForStream()
    .read(String.class, StreamOffset.fromStart("my-stream"));
1XADD my-stream * "_class" "java.lang.String" "_raw" "my-value"

ObjectRecord は、他のすべてのレコードとまったく同じ直列化プロセスを通過するため、MapRecord を返す型なし読み取り操作を使用してレコードを取得することもできます。

複素数値

ストリームに複雑な値を追加するには、次の 3 つの方法があります。

  • たとえばを使用して単純な値に変換します。文字列 JSON 表現。

  • 適切な RedisSerializer を使用して値を直列化します。

  • 値を HashMapper を使用した直列化に適した Map に変換します。

最初のバリアントは最も単純なものですが、ストリーム構造によって提供されるフィールド値機能を無視します。それでも、ストリーム内の値は他のコンシューマーが読み取ることができます。2 番目のオプションには、最初のオプションと同じ利点がありますが、すべてのコンシューマーがまったく同じ直列化メカニズムを実装する必要があるため、非常に具体的なコンシューマーの制限につながる可能性があります。HashMapper アプローチは、スチームハッシュ構造を利用するが、ソースをフラット化する、もう少し複雑なアプローチです。さらに他の消費者は、適切なシリアライザーの組み合わせが選択されている限り、レコードを読み取ることができます。

HashMappers は、ペイロードを特定のタイプの Map に変換します。ハッシュを(逆)直列化できる Hash-Key および Hash-Value シリアライザーを使用してください。
ObjectRecord<String, User> record = StreamRecords.newRecord()
    .in("user-logon")
    .ofObject(new User("night", "angel"));

redisTemplate()
    .opsForStream()
    .add(record); (1)

List<ObjectRecord<String, User>> records = redisTemplate()
    .opsForStream()
    .read(User.class, StreamOffset.fromStart("user-logon"));
1XADD ユーザーログオン * "_ class" "com.example.User" "firstname" "night" "lastname" "angel"

StreamOperations はデフォルトで ObjectHashMapper を使用します。StreamOperations を入手する際には、要件に適した HashMapper を提供できます。

redisTemplate()
    .opsForStream(new Jackson2HashMapper(true))
    .add(record); (1)
1XADD ユーザーログオン *「名」「夜」「@class」「com.example.User」「姓」「天使」

StreamMessageListenerContainer は、ドメインタイプで使用されている @TypeAlias を認識しない場合があります。これは、それらが MappingContext を介して解決される必要があるためです。RedisMappingContext は必ず initialEntitySet で初期化してください。

@Bean
RedisMappingContext redisMappingContext() {
    RedisMappingContext ctx = new RedisMappingContext();
    ctx.setInitialEntitySet(Collections.singleton(Person.class));
    return ctx;
}

@Bean
RedisConverter redisConverter(RedisMappingContext mappingContext) {
    return new MappingRedisConverter(mappingContext);
}

@Bean
ObjectHashMapper hashMapper(RedisConverter converter) {
    return new ObjectHashMapper(converter);
}

@Bean
StreamMessageListenerContainer streamMessageListenerContainer(RedisConnectionFactory connectionFactory, ObjectHashMapper hashMapper) {
    StreamMessageListenerContainerOptions<String, ObjectRecord<String, Object>> options = StreamMessageListenerContainerOptions.builder()
            .objectMapper(hashMapper)
            .build();

    return StreamMessageListenerContainer.create(connectionFactory, options);
}

10.12. Redis トランザクション

Redis は、multiexec および discard コマンドを介したトランザクション (英語) のサポートを提供します。これらの操作は RedisTemplate で利用できます。ただし、RedisTemplate は、トランザクション内のすべての操作を同じ接続で実行することが保証されているわけではありません。

Spring Data Redis は、Redis トランザクションを使用する場合など、同じ connection で複数の操作を実行する必要がある場合に使用する SessionCallback インターフェースを提供します。次の例では、multi メソッドを使用しています。

//execute a transaction
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
  public List<Object> execute(RedisOperations operations) throws DataAccessException {
    operations.multi();
    operations.opsForSet().add("key", "value1");

    // This will contain the results of all operations in the transaction
    return operations.exec();
  }
});
System.out.println("Number of items added to set: " + txResults.get(0));

RedisTemplate は、その値、ハッシュキー、ハッシュ値シリアライザーを使用して、exec のすべての結果を逆直列化してから返します。トランザクション結果のカスタムシリアライザーを渡すことができる追加の exec メソッドがあります。

バージョン 1.1 の時点で、RedisConnection および RedisTemplate の exec メソッドに重要な変更が加えられました。以前は、これらのメソッドはトランザクションの結果をコネクターから直接返していました。これは、データ型が RedisConnection のメソッドから返されるデータ型と異なることが多いことを意味します。例: zAdd は、要素がソートされたセットに追加されたかどうかを示すブール値を返します。ほとんどのコネクターはこの値を long として返し、Spring Data Redis が変換を実行します。もう 1 つの一般的な違いは、ほとんどのコネクターが set などの操作に対してステータス応答(通常は文字列 OK)を返すことです。これらの返信は通常、Spring Data Redis によって破棄されます。1.1 より前は、これらの変換は exec の結果に対して実行されていませんでした。また、結果は RedisTemplate で逆直列化されなかったため、多くの場合、生のバイト配列が含まれていました。この変更によってアプリケーションが破損する場合は、RedisConnectionFactory で convertPipelineAndTxResults を false に設定して、この動作を無効にします。

10.12.1. @Transactional サポート

デフォルトでは、RedisTemplate はマネージド Spring トランザクションに参加しません。@Transactional または TransactionTemplate を使用するときに RedisTemplate で Redis トランザクションを使用する場合は、setEnableTransactionSupport(true) を設定して、各 RedisTemplate のトランザクションサポートを明示的に有効にする必要があります。トランザクションサポートを有効にすると、RedisConnection が ThreadLocal に基づく現在のトランザクションにバインドされます。トランザクションがエラーなしで終了した場合、Redis トランザクションは EXEC でコミットされます。それ以外の場合は、DISCARD でロールバックされます。Redis トランザクションはバッチ指向です。進行中のトランザクション中に発行されたコマンドはキューに入れられ、トランザクションをコミットするときにのみ適用されます。

Spring Data Redis は、進行中のトランザクションで読み取り専用コマンドと書き込みコマンドを区別します。KEYS などの読み取り専用コマンドは、読み取りを許可するために新しい(スレッドにバインドされていない) RedisConnection にパイプされます。書き込みコマンドは RedisTemplate によってキューに入れられ、コミット時に適用されます。

次の例は、トランザクション管理を構成する方法を示しています。

例 3: トランザクション管理を可能にする構成
@Configuration
@EnableTransactionManagement                                 (1)
public class RedisTxContextConfiguration {

  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);              (2)
    return template;
  }

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    // jedis || Lettuce
  }

  @Bean
  public PlatformTransactionManager transactionManager() throws SQLException {
    return new DataSourceTransactionManager(dataSource());   (3)
  }

  @Bean
  public DataSource dataSource() throws SQLException {
    // ...
  }
}
1 宣言型トランザクション管理を有効にするために Spring コンテキストを構成します。
2 接続を現在のスレッドにバインドすることにより、トランザクションに参加するように RedisTemplate を構成します。
3 トランザクション管理には PlatformTransactionManager が必要です。Spring Data Redis には、PlatformTransactionManager 実装は付属していません。アプリケーションが JDBC を使用していると仮定すると、Spring Data Redis は、既存のトランザクションマネージャーを使用してトランザクションに参加できます。

次の例はそれぞれ、使用上の制約を示しています。

例 4: 使用上の制約
// must be performed on thread-bound connection
template.opsForValue().set("thing1", "thing2");

// read operation must be run on a free (not transaction-aware) connection
template.keys("*");

// returns null as values set within a transaction are not visible
template.opsForValue().get("thing1");

10.13. パイプライン

Redis は、パイプライン化 (英語) のサポートを提供します。これには、応答を待たずにサーバーに複数のコマンドを送信し、単一のステップで応答を読み取ることが含まれます。パイプライン処理は、同じリストに多くの要素を追加するなど、複数のコマンドを連続して送信する必要がある場合にパフォーマンスを向上させることができます。

Spring Data Redis は、パイプラインでコマンドを実行するためのいくつかの RedisTemplate メソッドを提供します。パイプライン操作の結果を気にしない場合は、標準の execute メソッドを使用して、pipeline 引数に true を渡すことができます。次の例に示すように、executePipelined メソッドは、提供された RedisCallback または SessionCallback をパイプラインで実行し、結果を返します。

//pop a specified number of items from a queue
List<Object> results = stringRedisTemplate.executePipelined(
  new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
      StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
      for(int i=0; i< batchSize; i++) {
        stringRedisConn.rPop("myqueue");
      }
    return null;
  }
});

前の例では、パイプラインのキューからアイテムの一括右ポップを実行します。resultsList には、ポップされたすべてのアイテムが含まれています。RedisTemplate は、その値、ハッシュキー、ハッシュ値シリアライザーを使用して、返される前にすべての結果を逆直列化するため、前の例で返される項目は文字列です。パイプライン化された結果のカスタムシリアライザーを渡すことができる追加の executePipelined メソッドがあります。

パイプライン化されたコマンドの結果を返すためにこの値は破棄されるため、RedisCallback から返される値は null である必要があることに注意してください。

Lettuce ドライバーは、コマンドが表示されたときにフラッシュするか、バッファーに入れるか、接続のクローズ時にコマンドを送信することを可能にする、きめ細かいフラッシュ制御をサポートします。

LettuceConnectionFactory factory = // ...
factory.setPipeliningFlushPolicy(PipeliningFlushPolicy.buffered(3)); (1)
1 ローカルでバッファリングし、3 番目のコマンドごとにフラッシュします。
バージョン 1.1 の時点で、RedisConnection および RedisTemplate の exec メソッドに重要な変更が加えられました。以前は、これらのメソッドはトランザクションの結果をコネクターから直接返していました。これは、データ型が RedisConnection のメソッドから返されるデータ型と異なることが多いことを意味します。例: zAdd は、要素がソートされたセットに追加されたかどうかを示すブール値を返します。ほとんどのコネクターはこの値を long として返し、Spring Data Redis が変換を実行します。もう 1 つの一般的な違いは、ほとんどのコネクターが set などの操作に対してステータス応答(通常は文字列 OK)を返すことです。これらの返信は通常、Spring Data Redis によって破棄されます。1.1 より前は、これらの変換は exec の結果に対して実行されていませんでした。また、結果は RedisTemplate で逆直列化されなかったため、多くの場合、生のバイト配列が含まれていました。この変更によってアプリケーションが破損する場合は、RedisConnectionFactory で convertPipelineAndTxResults を false に設定して、この動作を無効にします。

10.14. Redis スクリプティング

Redis バージョン 2.6 以降は、eval (英語) および evalsha (英語) コマンドを介した Lua スクリプトの実行をサポートします。Spring Data Redis は、直列化を処理し、Redis スクリプトキャッシュを自動的に使用するスクリプトを実行するための高レベルの抽象化を提供します。

スクリプトは、RedisTemplate および ReactiveRedisTemplate の execute メソッドを呼び出すことによって実行できます。どちらも、構成可能な ScriptExecutor (または ReactiveScriptExecutor)を使用して、提供されたスクリプトを実行します。デフォルトでは、ScriptExecutor (または ReactiveScriptExecutor)が、提供されたキーと引数の直列化とスクリプト結果の逆直列化を処理します。これは、テンプレートのキーと値のシリアライザーを介して行われます。スクリプト引数と結果のカスタムシリアライザーを渡すことができる追加のオーバーロードがあります。

デフォルトの ScriptExecutor は、スクリプトの SHA1 を取得し、最初に evalsha の実行を試み、スクリプトが Redis スクリプトキャッシュにまだ存在しない場合は eval にフォールバックすることにより、パフォーマンスを最適化します。

次の例では、Lua スクリプトを使用して、一般的な「チェックアンドセット」シナリオを実行します。これは、一連のコマンドをアトミックに実行する必要があり、あるコマンドの動作が別のコマンドの結果に影響されるため、Redis スクリプトの理想的な使用例です。

@Bean
public RedisScript<Boolean> script() {

  ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua"));
  return RedisScript.of(scriptSource, Boolean.class);
}
public class Example {

  @Autowired
  RedisScript<Boolean> script;

  public boolean checkAndSet(String expectedValue, String newValue) {
    return redisTemplate.execute(script, singletonList("key"), asList(expectedValue, newValue));
  }
}
-- checkandset.lua
local current = redis.call('GET', KEYS[1])
if current == ARGV[1]
  then redis.call('SET', KEYS[1], ARGV[2])
  return true
end
return false

上記のコードは、ブール値を返すことが期待される checkandset.lua というファイルを指す RedisScript を構成します。スクリプト resultType は、LongBooleanList のいずれか、または逆直列化された値のタイプである必要があります。スクリプトが使い捨てステータス(具体的には OK)を返す場合は、null にすることもできます。

スクリプトを実行するたびにスクリプトの SHA1 が再計算されないように、アプリケーションコンテキストで DefaultRedisScript の単一インスタンスを構成することが理想的です。

次に、上記の checkAndSet メソッドがスクリプトを実行します。スクリプトは、トランザクションまたはパイプラインの一部として SessionCallback 内で実行できます。詳細については、「Redis トランザクション」および「パイプライン」を参照してください。

Spring Data によって提供されるスクリプトサポート Redis では、Spring タスクおよびスケジューラーの抽象化を使用して、Redis スクリプトを定期的に実行するようにスケジュールすることもできます。詳細については、Spring Framework のドキュメントを参照してください。

10.15. サポートクラス

パッケージ org.springframework.data.redis.support は、バッキングストアとして Redis に依存するさまざまな再利用可能なコンポーネントを提供します。現在、パッケージには、アトミック (標準 Javadoc) カウンターや JDK コレクション (標準 Javadoc) など、Redis に加えてさまざまな JDK ベースのインターフェース実装が含まれています。

アトミックカウンターを使用すると、Redis キーの増分を簡単にラップでき、コレクションを使用すると、ストレージの露出や API リークを最小限に抑えて Redis キーを簡単に管理できます。特に、RedisSet および RedisZSet インターフェースは、intersection および union などの Redis でサポートされているセット操作への簡単なアクセスを提供します。RedisList は、Redis の上に ListQueue および Deque 契約(および同等のブロッキング兄弟)を実装し、ストレージを FIFO(First-In-First-Out)、LIFO(Last-In-First-Out)、またはキャップ付きコレクションとして公開します。最小限の構成。次の例は、RedisList を使用する Bean の構成を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="
  http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="queue" class="org.springframework.data.redis.support.collections.DefaultRedisList">
    <constructor-arg ref="redisTemplate"/>
    <constructor-arg value="queue-key"/>
  </bean>

</beans>

次の例は、Deque の Java 構成例を示しています。

public class AnotherExample {

  // injected
  private Deque<String> queue;

  public void addTag(String tag) {
    queue.push(tag);
  }
}

前の例に示されているように、消費コードは実際のストレージ実装から切り離されています。実際、Redis がそで使用されているという兆候はありません。これにより、開発環境から本番環境への移行が透過的になり、テスト容易性が大幅に向上します(Redis 実装はインメモリ実装に置き換えることができます)。

10.15.1. Spring キャッシュ抽象化のサポート

2.0 で変更

Spring Redis は、org.springframework.data.redis.cache パッケージを介した Spring キャッシュ抽象化の実装を提供します。Redis をバッキング実装として使用するには、次のように RedisCacheManager を構成に追加します。

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
	return RedisCacheManager.create(connectionFactory);
}

RedisCacheManager の動作は RedisCacheManagerBuilder で構成でき、デフォルトの RedisCacheConfiguration、トランザクションの動作、および事前定義されたキャッシュを設定できます。

RedisCacheManager cm = RedisCacheManager.builder(connectionFactory)
	.cacheDefaults(defaultCacheConfig())
	.withInitialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
	.transactionAware()
	.build();

前の例に示されているように、RedisCacheManager では、キャッシュごとに構成を定義できます。

RedisCacheManager で作成された RedisCache の動作は、RedisCacheConfiguration で定義されます。この構成では、次の例に示すように、バイナリストレージ形式との間で変換するためのキーの有効期限、プレフィックス、RedisSerializer 実装を設定できます。

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(1))
	.disableCachingNullValues();

RedisCacheManager は、バイナリ値の読み取りと書き込みのために、デフォルトでロックフリーの RedisCacheWriter になります。ロックフリーキャッシングはスループットを向上させます。エントリロックがない場合、putIfAbsent メソッドと clean メソッドの非アトミックコマンドが重複する可能性があります。これらのコマンドでは、Redis に複数のコマンドを送信する必要があるためです。対応するロックは、明示的なロックキーを設定し、このキーの存在をチェックすることでコマンドの重複を防ぎます。これにより、追加のリクエストと潜在的なコマンド待機時間が発生します。

次のようにロック動作にオプトインすることができます。

RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.lockingRedisCacheWriter())
	.cacheDefaults(defaultCacheConfig())
	...

デフォルトでは、キャッシュエントリの key には、実際のキャッシュ名の前に 2 つのコロンが続きます。この動作は、静的プレフィックスと計算プレフィックスに変更できます。

次の例は、静的プレフィックスを設定する方法を示しています。

// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith("(͡°   ᴥ   ͡°)");

The following example shows how to set a computed prefix:

// computed key prefix
RedisCacheConfiguration.defaultCacheConfig().computePrefixWith(cacheName -> " ¯ \_(ツ)_/ ¯ " + cacheName);

次の表に、RedisCacheManager のデフォルト設定を示します。

表 7: RedisCacheManager のデフォルト
設定

キャッシュライター

ノンロック

キャッシュ構成

RedisCacheConfiguration#defaultConfiguration

初期キャッシュ

なし

トランザクション対応

いいえ

次の表に、RedisCacheConfiguration のデフォルト設定を示します。

表 8: RedisCacheConfiguration のデフォルト
キーの有効期限 なし

キャッシュ null

はい

プレフィックスキー

はい

デフォルト接頭部

実際のキャッシュ名

キーシリアライザー

StringRedisSerializer

バリューシリアライザー

JdkSerializationRedisSerializer

変換サービス

デフォルトのキャッシュキーコンバーターを備えた DefaultFormattingConversionService 

デフォルトでは RedisCache、統計は無効になっています。RedisCacheManagerBuilder.enableStatistics() を使用して、RedisCache#getStatistics() を介してローカルのヒットミスを収集し、収集されたデータのスナップショットを返します。

11. リアクティブ Redis サポート

このセクションでは、リアクティブ Redis のサポートと開始方法について説明します。リアクティブ Redis サポートは、当然、命令型 Redis サポートと一定の重複があります。

11.1. Redis の要件

Spring Data Redis は現在、唯一のリアクティブ Java コネクターとして Lettuce: GitHub (英語) と統合されています。プロジェクト Reactor (英語) は反応性組成ライブラリとして使用されます。

11.2. リアクティブドライバーを使用した Redis への接続

Redis および Spring を使用する場合の最初のタスクの 1 つは、IoC コンテナーを介してストアに接続することです。これを行うには、Java コネクター(またはバインディング)が必要です。選択したライブラリに関係なく、アクティブな connections から Redis を操作および取得するには、org.springframework.data.redis.connection パッケージとその ReactiveRedisConnection および ReactiveRedisConnectionFactory インターフェースを使用する必要があります。

11.2.1. Redis 動作モード

Redis は、スタンドアロンサーバーとして、Redis Sentinel を使用して、または Redis クラスターモードで実行できます。Lettuce: GitHub (英語) は、前述のすべての接続タイプをサポートします。

11.2.2. ReactiveRedisConnection および ReactiveRedisConnectionFactory

ReactiveRedisConnection は、Redis バックエンドとの通信を処理するため、Redis 通信の中核です。また、基になるドライバー例外を Spring の一貫した DAO 例外階層に自動的に変換するため、操作のセマンティクスは同じままであるため、コードを変更せずにコネクターを切り替えることができます。

ReactiveRedisConnectionFactory は、アクティブな ReactiveRedisConnection インスタンスを作成します。さらに、ファクトリは PersistenceExceptionTranslator インスタンスとして機能します。つまり、宣言されると、透過的な例外変換(たとえば、@Repository アノテーションと AOP を使用した例外変換)を実行できます。詳細については、Spring Framework ドキュメントの専用セクションを参照してください。

基盤となる構成に応じて、ファクトリは新しい接続または既存の接続を返すことができます(プールまたは共有ネイティブ接続が使用されている場合)。
ReactiveRedisConnectionFactory を操作する最も簡単な方法は、IoC コンテナーを介して適切なコネクターを構成し、それを using クラスに挿入することです。

11.2.3. Lettuce コネクターの構成

Lettuce: GitHub (英語) は、org.springframework.data.redis.connection.lettuce パッケージを介して Spring Data Redis によってサポートされます。

Lettuce の ReactiveRedisConnectionFactory は、次のように設定できます。

@Bean
public ReactiveRedisConnectionFactory connectionFactory() {
  return new LettuceConnectionFactory("localhost", 6379);
}

次の例は、LettuceClientConfigurationBuilder を使用する、SSL やタイムアウトなどのより高度な構成を示しています。

@Bean
public ReactiveRedisConnectionFactory lettuceConnectionFactory() {

  LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
    .useSsl().and()
    .commandTimeout(Duration.ofSeconds(2))
    .shutdownTimeout(Duration.ZERO)
    .build();

  return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379), clientConfig);
}

クライアント構成の微調整の詳細については、LettuceClientConfiguration (Javadoc) を参照してください。

11.3. ReactiveRedisTemplate を介したオブジェクトの操作

ほとんどのユーザーは、ReactiveRedisTemplate とそれに対応するパッケージ org.springframework.data.redis.core を使用する可能性があります。その豊富な機能セットにより、テンプレートは実際、Redis モジュールの中心的なクラスです。テンプレートは、Redis インタラクションの高レベルの抽象化を提供します。ReactiveRedisConnection は、バイナリ値を受け入れて返す低レベルのメソッド(ByteBuffer)を提供しますが、テンプレートは直列化と接続管理を処理し、そのような詳細を処理する必要がありません。

さらに、テンプレートは、次の表に従って、特定のタイプに対して機能するための豊富で汎用化されたインターフェースを提供する操作ビュー(Redis コマンドリファレンス (英語) からのグループ化に続く)を提供します。

表 9: 運用ビュー
インターフェース 説明

キータイプの操作

ReactiveGeoOperations

GEOADDGEORADIUS などの Redis 地理空間操作など)

ReactiveHashOperations

Redis ハッシュ演算

ReactiveHyperLogLogOperations

Redis HyperLogLog 操作など (PFADDPFCOUNT、その他)

ReactiveListOperations

Redis リスト操作

ReactiveSetOperations

Redis セット操作

ReactiveValueOperations

Redis 文字列(または値)操作

ReactiveZSetOperations

Redis zset(またはソートされたセット)操作

構成が完了すると、テンプレートはスレッドセーフになり、複数のインスタンスで再利用できます。

ReactiveRedisTemplate は、ほとんどの操作に Java ベースのシリアライザーを使用します。これは、テンプレートによって書き込まれた、または読み取られたオブジェクトが、RedisElementWriter または RedisElementReader を介して直列化または逆直列化されることを意味します。直列化コンテキストは構築時にテンプレートに渡され、Redis モジュールは org.springframework.data.redis.serializer パッケージで利用可能ないくつかの実装を提供します。詳細については、シリアライザーを参照してください。

次の例は、Mono を返すために使用されている ReactiveRedisTemplate を示しています。

@Configuration
class RedisConfiguration {

  @Bean
  ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
    return new ReactiveRedisTemplate<>(factory, RedisSerializationContext.string());
  }
}
public class Example {

  @Autowired
  private ReactiveRedisTemplate<String, String> template;

  public Mono<Long> addLink(String userId, URL url) {
    return template.opsForList().leftPush(userId, url.toExternalForm());
  }
}

11.4. 文字列に焦点を当てたコンビニエンスクラス

Redis に格納されているキーと値が java.lang.String であることが非常に一般的であるため、Redis モジュールは ReactiveRedisTemplate の文字列ベースの拡張機能を提供します: ReactiveStringRedisTemplate。これは、集中的な String 操作に便利なワンストップソリューションです。テンプレートは、String キーにバインドされるだけでなく、文字列ベースの RedisSerializationContext を使用します。これは、格納されているキーと値が人間が読める形式であることを意味します(Redis とコードの両方で同じエンコーディングが使用されていると想定)。次の例は、使用中の ReactiveStringRedisTemplate を示しています。

@Configuration
class RedisConfiguration {

  @Bean
  ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
    return new ReactiveStringRedisTemplate<>(factory);
  }
}
public class Example {

  @Autowired
  private ReactiveStringRedisTemplate redisTemplate;

  public Mono<Long> addLink(String userId, URL url) {
    return redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
  }
}

11.5. Redis メッセージング / PubSub

Spring Data は、Redis 専用のメッセージング統合を提供します。これは、Spring フレームワークの JMS 統合と機能および命名が非常に似ています。実際、Spring での JMS サポートに精通しているユーザーは、自宅にいるように感じるはずです。

Redis メッセージングは、機能の 2 つの領域、つまりメッセージの生成または公開と消費またはサブスクリプションに大別できます。ショートカット pubsub(パブリッシュ / サブスクライブ)です。ReactiveRedisTemplate クラスは、メッセージの生成に使用されます。非同期受信の場合、Spring Data は、メッセージのストリームを消費するために使用される専用のメッセージリスナーコンテナーを提供します。ReactiveRedisTemplate をサブスクライブするだけの目的で、リスナーコンテナーを利用する代わりの方法を取り除いたものを提供します。

パッケージ org.springframework.data.redis.connection および org.springframework.data.redis.listener は、Redis メッセージングを使用するためのコア機能を提供します。

11.5.1. メッセージの送信 / 公開

メッセージを公開するには、他の操作と同様に、低レベルの ReactiveRedisConnection または高レベルの ReactiveRedisTemplate のいずれかを使用できます。どちらのエンティティも、送信する必要のあるメッセージと宛先チャネルを引数として受け入れる公開メソッドを提供します。ReactiveRedisConnection には生データが必要ですが、ReactiveRedisTemplate では、任意のオブジェクトをメッセージとして渡すことができます。

// send message through ReactiveRedisConnection
ByteBuffer msg =  …
ByteBuffer channel =  …
Mono<Long> publish = con.publish(msg, channel);

// send message through ReactiveRedisTemplate
ReactiveRedisTemplate template =  …
Mono<Long> publish = template.convertAndSend("channel", "message");

11.5.2. メッセージの受信 / サブスクライブ

受信側では、チャネルに直接名前を付けるか、パターンマッチングを使用して、1 つまたは複数のチャネルにサブスクライブできます。後者のアプローチは、1 つのコマンドで複数のサブスクリプションを作成できるだけでなく、サブスクリプション時にまだ作成されていないチャネルをリッスンできるため(パターンに一致する限り)非常に便利です。

低レベルでは、ReactiveRedisConnection は subscribe および pSubscribe メソッドを提供し、それぞれチャネルごとにパターンごとにサブスクライブするための Redis コマンドをマップします。複数のチャネルまたはパターンを引数として使用できることに注意してください。サブスクリプションを変更するには、ReactiveSubscription のチャネルとパターンを照会するだけです。

Spring Data のリアクティブサブスクリプションコマンド Redis はノンブロッキングであり、要素を発行せずに終了する場合があります。

上記のように、サブスクライブすると、接続はメッセージの待機を開始します。新しいサブスクリプションを追加するか、既存のサブスクリプションを変更 / キャンセルする以外に、他のコマンドを呼び出すことはできません。subscribepSubscribeunsubscribe または pUnsubscribe 以外のコマンドは無効であり、例外が発生します。

メッセージを受信するには、メッセージストリームを取得する必要があります。サブスクリプションは、その特定のサブスクリプションに登録されているチャネルとパターンのメッセージのみを公開することに注意してください。メッセージストリーム自体は、需要に関係なく要素を生成するホットシーケンスです。メッセージバッファを使い果たしないように、十分なデマンドを登録してください。

メッセージリスナコンテナー

Spring Data は、ユーザーに代わって変換とサブスクリプション状態管理のすべての面倒な作業を行う ReactiveRedisMessageListenerContainer を提供します。

ReactiveRedisMessageListenerContainer は、メッセージリスナーコンテナーとして機能します。これは、Redis チャネルからメッセージを受信し、逆直列化が適用されたチャネルメッセージを送信するメッセージのストリームを公開するために使用されます。メッセージを受信するための登録、リソースの取得と解放、例外変換などを処理します。これにより、アプリケーション開発者は、メッセージの受信(およびメッセージへの応答)に関連する(おそらく複雑な)ビジネスロジックを記述し、定型的な Redis インフラストラクチャの懸念事項をフレームワークに委譲できます。メッセージストリームは、発行者のサブスクリプション時に Redis にサブスクリプションを登録し、サブスクリプションがキャンセルされた場合は登録を解除します。

さらに、アプリケーションのフットプリントを最小限に抑えるために、ReactiveRedisMessageListenerContainer では、サブスクリプションを共有していなくても、1 つの接続と 1 つのスレッドを複数のリスナーで共有できます。アプリケーションが追跡するリスナーまたはチャネルの数に関係なく、実行時のコストはその存続期間を通じて同じままです。さらに、コンテナーではランタイム構成の変更が可能であるため、アプリケーションの実行中にリスナーを追加または削除でき、再起動する必要はありません。さらに、コンテナーは遅延サブスクリプションアプローチを使用し、必要な場合にのみ ReactiveRedisConnection を使用します。すべてのリスナーがサブスクライブ解除されている場合、クリーンアップが自動的に実行されます。

メッセージリスナーコンテナー自体は、外部スレッドリソースを必要としません。ドライバースレッドを使用してメッセージを公開します。

ReactiveRedisConnectionFactory factory =  …
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);

Flux<ChannelMessage<String, String>> stream = container.receive(ChannelTopic.of("my-channel"));
テンプレート API を介したサブスクライブ

上記のように、ReactiveRedisTemplate を直接使用してチャンネル / パターンをサブスクライブできます。このアプローチは、最初のサブスクリプションの後にサブスクリプションを追加するオプションを失うため、制限されたソリューションですが、簡単です。それでも、たとえばを使用して、返された Flux を介してメッセージストリームを制御できます。take(Duration)。読み取りが完了すると、エラーまたはキャンセル時に、バインドされているすべてのリソースが再び解放されます。

redisTemplate.listenToChannel("channel1", "channel2").doOnNext(msg -> {
    // message processing ...
}).subscribe();

11.6. リアクティブスクリプティング

ReactiveRedisTemplate を介してアクセスするのに最適な ReactiveScriptExecutor を使用すると、リアクティブインフラストラクチャで Redis スクリプトを実行できます。

public class Example {

  @Autowired
  private ReactiveRedisTemplate<String, String> template;

  public Flux<Long> theAnswerToLife() {

    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    script.setLocation(new ClassPathResource("META-INF/scripts/42.lua"));
    script.setResultType(Long.class);

    return reactiveTemplate.execute(script);
  }
}

スクリプトコマンドの詳細については、スクリプトのセクションを参照してください。

12. Redis クラスター

Redis クラスター (英語) を使用するには、Redis サーバーバージョン 3.0 + が必要です。詳細については、クラスターチュートリアル (英語) を参照してください。

12.1. Redis クラスターの有効化

クラスターのサポートは、クラスター化されていない通信と同じ構成要素に基づいています。RedisConnection の拡張である RedisClusterConnection は、Redis クラスターとの通信を処理し、エラーを SpringDAO 例外階層に変換します。RedisClusterConnection インスタンスは、次の例に示すように、RedisConnectionFactory を使用して作成されます。RedisConnectionFactory は、関連付けられた RedisClusterConfiguration を使用して設定する必要があります。

例 5: Redis クラスターの RedisConnectionFactory 構成のサンプル
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {

    /*
     * spring.redis.cluster.nodes[0] = 127.0.0.1:7379
     * spring.redis.cluster.nodes[1] = 127.0.0.1:7380
     * ...
     */
    List<String> nodes;

    /**
     * Get initial collection of known cluster nodes in format {@code host:port}.
     *
     * @return
     */
    public List<String> getNodes() {
        return nodes;
    }

    public void setNodes(List<String> nodes) {
        this.nodes = nodes;
    }
}

@Configuration
public class AppConfig {

    /**
     * Type safe representation of application.properties
     */
    @Autowired ClusterConfigurationProperties clusterProperties;

    public @Bean RedisConnectionFactory connectionFactory() {

        return new JedisConnectionFactory(
            new RedisClusterConfiguration(clusterProperties.getNodes()));
    }
}

RedisClusterConfiguration は PropertySource を介して定義することもでき、次のプロパティがあります。

プロパティの構成
  • spring.redis.cluster.nodes: ホスト:ポートのペアのコンマ区切りのリスト。

  • spring.redis.cluster.max-redirects: 許可されたクラスターリダイレクトの数。

初期構成は、ドライバーライブラリをクラスターノードの初期セットにポイントします。ライブクラスターの再構成による変更は、ネイティブドライバーにのみ保持され、構成に書き戻されることはありません。

12.2. Redis クラスター接続の操作

前述のように、Redis クラスターは、単一ノードの Redis や、Sentinel が監視するマスターレプリカ環境とは異なる動作をします。これは、自動シャーディングが、ノード全体に分散されている 16384 スロットの 1 つにキーをマップするためです。複数のキーを含むコマンドは、クロススロットエラーを回避するために、すべてのキーがまったく同じスロットにマップされていることを表明する必要があります。単一のクラスターノードは、専用のキーセットのみを提供します。1 つの特定のサーバーに対して発行されたコマンドは、そのサーバーによって提供されるキーに対してのみ結果を返します。簡単な例として、KEYS コマンドについて考えてみます。クラスター環境のサーバーに発行されると、リクエストが送信されたノードによって提供されるキーのみが返され、クラスター内のすべてのキーが返されるとは限りません。クラスター環境ですべてのキーを取得するには、すべての既知のマスターノードからキーを読み取る必要があります。

対応するスロットサービングノードへの特定のキーのリダイレクトはドライバーライブラリによって処理されますが、ノード間で情報を収集したり、クラスター内のすべてのノードにコマンドを送信したりするなどの高レベルの機能は、RedisClusterConnection でカバーされます。前のキーの例を取得すると、これは、keys(pattern) メソッドがクラスター内のすべてのマスターノードを取得し、同時にすべてのマスターノードで KEYS コマンドを実行しながら、結果を取得して累積されたキーのセットを返すことを意味します。単一ノードのキーをリクエストするだけで、RedisClusterConnection はそれらのメソッド(たとえば、keys(node, pattern))にオーバーロードを提供します。

RedisClusterNode は、RedisClusterConnection.clusterGetNodes から取得することも、ホストとポートまたはノード ID を使用して構築することもできます。

次の例は、クラスター全体で実行されている一連のコマンドを示しています。

例 6: クラスター全体で実行されるコマンドのサンプル
[email protected] (英語)  :7379 > cluster nodes

6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460                      (1)
7bb78c... 127.0.0.1:7380 master - 0 1449730618304 2 connected 5461-10922       (2)
164888... 127.0.0.1:7381 master - 0 1449730618304 3 connected 10923-16383      (3)
b8b5ee... 127.0.0.1:7382 slave 6b38bb... 0 1449730618304 25 connected          (4)
RedisClusterConnection connection = connectionFactory.getClusterConnnection();

connection.set("thing1", value);                                               (5)
connection.set("thing2", value);                                               (6)

connection.keys("*");                                                          (7)

connection.keys(NODE_7379, "*");                                               (8)
connection.keys(NODE_7380, "*");                                               (9)
connection.keys(NODE_7381, "*");                                               (10)
connection.keys(NODE_7382, "*");                                               (11)
1 スロット 0 〜 5460 にサービスを提供するマスターノードが 7382 のレプリカに複製されました
2 スロット 5461 から 10922 にサービスを提供するマスターノード
3 スロット 10923 から 16383 にサービスを提供するマスターノード
47379 でマスターのレプリカントを保持しているレプリカノード
5 リクエストは 7381 サービングスロット 12182 のノードにルーティングされました
6 リクエストは 7379 サービングスロット 5061 のノードにルーティングされました
77379, 7380, 7381 のノードにルーティングされたリクエスト→ [thing1、thing2]
8 リクエストは 7379 のノードにルーティングされました→ [thing2]
9 リクエストは 7380 のノードにルーティングされました→ []
10 リクエストは 7381 のノードにルーティングされました→ [thing1]
11 リクエストは 7382 のノードにルーティングされました→ [thing2]

すべてのキーが同じスロットにマップされると、ネイティブドライバーライブラリは、MGET などのクロススロットリクエストを自動的に処理します。ただし、これが当てはまらない場合、RedisClusterConnection はスロットサービングノードに対して複数の並列 GET コマンドを実行し、累積された結果を再び返します。これはシングルスロットアプローチよりもパフォーマンスが低いため、注意して使用する必要があります。疑わしい場合は、同じスロット番号にマップされる {my-prefix}.thing1 や {my-prefix}.thing2 などのプレフィックスをカーリー括弧に指定して、キーを同じスロットに固定することを検討してください。次の例は、クロススロットリクエストの処理を示しています。

例 7: クロススロットリクエスト処理のサンプル
[email protected] (英語)  :7379 > cluster nodes

6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460                      (1)
7bb...
RedisClusterConnection connection = connectionFactory.getClusterConnnection();

connection.set("thing1", value);           // slot: 12182
connection.set("{thing1}.thing2", value);  // slot: 12182
connection.set("thing2", value);           // slot:  5461

connection.mGet("thing1", "{thing1}.thing2");                                  (2)

connection.mGet("thing1", "thing2");                                           (3)
1 前のサンプルと同じ構成。
2 キーは同じスロットにマップされます。→ 127.0.0.1:7381MGETthing1 {thing1} .thing2
3 キーは異なるスロットにマップされ、対応するノードにルーティングされる単一のスロットに分割されます
→ 127.0.0.1:7379GETthing2
→ 127.0.0.1:7381GETthing1
上記の例は、Spring Data Redis が続く一般的な戦略を示しています。一部の操作では、目的のコマンドを計算するために大量のデータをメモリにロードする必要がある場合があることに注意してください。さらに、すべてのクロススロットリクエストを複数のシングルスロットリクエストに安全に移植できるわけではなく、誤用するとエラーが発生します(たとえば、PFCOUNT)。

12.3. RedisTemplate および ClusterOperations の操作

RedisTemplate の汎用、構成、使用箇所については、RedisTemplate を介したオブジェクトの操作セクションを参照してください。

JSON RedisSerializers のいずれかを使用して RedisTemplate#keySerializer を設定する場合は、JSON 構造の変更がハッシュスロットの計算にすぐに影響するため、注意してください。

RedisTemplate は、RedisTemplate.opsForCluster() から取得できる ClusterOperations インターフェースを介してクラスター固有の操作へのアクセスを提供します。これにより、テンプレート用に構成された直列化および逆直列化機能を保持しながら、クラスター内の単一ノードでコマンドを明示的に実行できます。また、管理コマンド(CLUSTER MEET など)またはより高レベルの操作(再シャーディングなど)も提供します。

次の例は、RedisTemplate を使用して RedisClusterConnection にアクセスする方法を示しています。

例 8: RedisTemplate を使用した RedisClusterConnection へのアクセス
ClusterOperations clusterOps = redisTemplate.opsForCluster();
clusterOps.shutdown(NODE_7379);                                              (1)
17379 でノードをシャットダウンし、指を交差させると、引き継ぐことができるレプリカが配置されます。

13. Redis リポジトリ

Redis リポジトリを使用すると、ドメインオブジェクトを Redis ハッシュにシームレスに変換して保存し、カスタムマッピング戦略を適用し、セカンダリインデックスを使用できます。

Redis リポジトリは、少なくとも Redis サーバーバージョン 2.8.0 を必要とし、トランザクションでは機能しません。トランザクションサポート無効になっている RedisTemplate を必ず使用してください。

13.1. 使用方法

Spring Data Redis を使用すると、次の例に示すように、ドメインエンティティを簡単に実装できます。

例 9: サンプルの個人エンティティ
@RedisHash("people")
public class Person {

  @Id String id;
  String firstname;
  String lastname;
  Address address;
}

ここには非常に単純なドメインオブジェクトがあります。タイプに @RedisHash アノテーションがあり、org.springframework.data.annotation.Id アノテーションが付けられた id という名前のプロパティがあることに注意してください。これらの 2 つの項目は、ハッシュを永続化するために使用される実際のキーを作成する責任があります。

@Id でアノテーションが付けられたプロパティ、および id という名前のプロパティは、識別子プロパティと見なされます。アノテーションのあるものは他のものよりも好まれます。

ストレージと取得を担当するコンポーネントを実際に作成するには、次の例に示すように、リポジトリインターフェースを定義する必要があります。

例 10: 個人エンティティを永続化するための基本的なリポジトリインターフェース
public interface PersonRepository extends CrudRepository<Person, String> {

}

リポジトリは CrudRepository を継承しているため、基本的な CRUD およびファインダー操作を提供します。次の例に示すように、物を接着するために間に必要なのは、対応する Spring 構成です。

例 11: Redis リポジトリ用の JavaConfig
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  @Bean
  public RedisConnectionFactory connectionFactory() {
    return new JedisConnectionFactory();
  }

  @Bean
  public RedisTemplate<?, ?> redisTemplate() {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    return template;
  }
}

上記の設定を前提として、次の例に示すように、PersonRepository をコンポーネントに注入できます。

例 12: 個人エンティティへのアクセス
@Autowired PersonRepository repo;

public void basicCrudOperations() {

  Person rand = new Person("rand", "al'thor");
  rand.setAddress(new Address("emond's field", "andor"));

  repo.save(rand);                                         (1)

  repo.findOne(rand.getId());                              (2)

  repo.count();                                            (3)

  repo.delete(rand);                                       (4)
}
1 現在の値が null の場合、新しい id を生成するか、すでに設定されている id 値を再利用し、keyspace:id のパターンを持つキーを使用して Redis ハッシュ内にタイプ Person のプロパティを格納します。この場合は people:5d67b7e1-8640-4475-beeb-c666fab4c0e5 である可能性があります。
2 提供された id を使用して、keyspace:id に格納されているオブジェクトを取得します。
3Person 上の @RedisHash によって定義された、キースペース people 内で使用可能なエンティティの総数をカウントします。
4 指定されたオブジェクトのキーを Redis から削除します。

13.2. オブジェクトマッピングの基礎

このセクションでは、Spring Data オブジェクトマッピング、オブジェクト作成、フィールドとプロパティへのアクセス、可変性と不変性の基礎について説明します。このセクションは、基になるデータストア(JPA など)のオブジェクトマッピングを使用しない Spring Data モジュールにのみ適用されることに注意してください。また、インデックス、列名やフィールド名のカスタマイズなど、ストア固有のオブジェクトマッピングについては、ストア固有のセクションを参照してください。

Spring Data オブジェクトマッピングの中心的なロールは、ドメインオブジェクトのインスタンスを作成し、ストアネイティブデータ構造をそれらにマッピングすることです。つまり、2 つの基本的な手順が必要です。

  1. 公開されたコンストラクターの 1 つを使用したインスタンスの作成。

  2. すべての公開されたプロパティを具体化するインスタンスの設定。

13.2.1. オブジェクト作成

Spring Data は、そのタイプのオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。

  1. 引数なしのコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。

  2. 引数を取る単一のコンストラクターがある場合は、それが使用されます。

  3. 引数を取る複数のコンストラクターがある場合、Spring Data が使用するコンストラクターに @PersistenceConstructor のアノテーションを付ける必要があります。

値の解決は、コンストラクターの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含む、プロパティが設定されるかのように解決が実行されます。また、これには、クラスファイルで使用可能なパラメーター名情報、またはコンストラクターに存在する @ConstructorProperties アノテーションのいずれかが必要です。

値の解決は、ストア固有の SpEL 式を使用した Spring Framework の @Value 値アノテーションを使用してカスタマイズできます。詳細については、ストア固有のマッピングに関するセクションを参照してください。

オブジェクト作成の詳細

リフレクションのオーバーヘッドを回避するために、Spring Data オブジェクトの作成では、デフォルトで実行時に生成されるファクトリクラスを使用します。これにより、ドメインクラスコンストラクターが直接呼び出されます。つまりこの例のタイプ:

class Person {
  Person(String firstname, String lastname) {  …  }
}

実行時にこれと意味的に同等のファクトリクラスを作成します。

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

これにより、リフレクションよりも約 10% パフォーマンスが向上します。ドメインクラスがこのような最適化の対象となるには、一連の制約に従う必要があります。

  • プライベートクラスであってはなりません

  • 非静的内部クラスであってはなりません

  • CGLib プロキシクラスであってはなりません

  • Spring Data で使用されるコンストラクターはプライベートであってはなりません

これらの条件のいずれかが一致する場合、Spring Data はリフレクションを介してエンティティのインスタンス化にフォールバックします。

13.2.2. プロパティ設定

エンティティのインスタンスが作成されると、Spring Data はそのクラスの残りのすべての永続プロパティを設定します。エンティティのコンストラクターによって既に入力されていない場合(つまり、コンストラクターの引数リストを介して使用される場合)、ID プロパティが最初に入力され、循環オブジェクト参照の解決が可能になります。その後、コンストラクターによってまだ設定されていないすべての非一時的なプロパティがエンティティインスタンスに設定されます。そのために、次のアルゴリズムを使用します。

  1. プロパティが不変であるが with …  メソッドを公開している場合(以下を参照)、with …  メソッドを使用して、新しいプロパティ値を持つ新しいエンティティインスタンスを作成します。

  2. プロパティアクセス(つまり、getter および setter を介したアクセス)が定義されている場合、setter メソッドを呼び出しています。

  3. プロパティが変更可能な場合、フィールドを直接設定します。

  4. プロパティが不変の場合、永続化操作(オブジェクト作成を参照)で使用されるコンストラクターを使用して、インスタンスのコピーを作成します。

  5. デフォルトでは、フィールド値を直接設定します。

プロパティ設定の詳細

オブジェクト構築の最適化と同様に、Spring Data ランタイム生成のアクセサークラスを使用して、エンティティインスタンスと対話します。

class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}
例 13: 生成されたプロパティアクセサー
class PersonPropertyAccessor implements PersistentPropertyAccessor {

  private static final MethodHandle firstname;              (2)

  private Person person;                                    (1)

  public void setProperty(PersistentProperty property, Object value) {

    String name = property.getName();

    if ("firstname".equals(name)) {
      firstname.invoke(person, (String) value);             (2)
    } else if ("id".equals(name)) {
      this.person = person.withId((Long) value);            (3)
    } else if ("lastname".equals(name)) {
      this.person.setLastname((String) value);              (4)
    }
  }
}
1PropertyAccessor は、基礎となるオブジェクトの可変インスタンスを保持します。これは、そうでなければ不変のプロパティの変更を可能にするためです。
2 デフォルトでは、Spring Data はフィールドアクセスを使用してプロパティ値を読み書きします。private フィールドの可視性ルールに従って、MethodHandles はフィールドとの対話に使用されます。
3 クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。withId(…) を呼び出すと、新しい Person オブジェクトが作成されます。後続のすべての変更は、新しいインスタンスで行われ、前のインスタンスは変更されません。
4property-access を使用すると、MethodHandles を使用せずに直接メソッドを呼び出すことができます。

これにより、リフレクションよりも約 25% パフォーマンスが向上します。ドメインクラスがこのような最適化の対象となるには、一連の制約に従う必要があります。

  • タイプは、デフォルトまたは java パッケージに存在してはなりません。

  • 型とそのコンストラクターは public でなければなりません

  • 内部クラスである型は static でなければなりません。

  • 使用される Java ランタイムは、元の ClassLoader でクラスを宣言できるようにする必要があります。Java 9 以降には特定の制限があります。

デフォルトでは、Spring Data は生成されたプロパティアクセサーを使用しようとし、制限が検出された場合はリフレクションベースのものにフォールバックします。

次のエンティティを見てみましょう。

例 14: サンプルエンティティ
class Person {

  private final @Id Long id;                                                (1)
  private final String firstname, lastname;                                 (2)
  private final LocalDate birthday;
  private final int age;                                                    (3)

  private String comment;                                                   (4)
  private @AccessType(Type.PROPERTY) String remarks;                        (5)

  static Person of(String firstname, String lastname, LocalDate birthday) { (6)

    return new Person(null, firstname, lastname, birthday,
      Period.between(birthday, LocalDate.now()).getYears());
  }

  Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)

    this.id = id;
    this.firstname = firstname;
    this.lastname = lastname;
    this.birthday = birthday;
    this.age = age;
  }

  Person withId(Long id) {                                                  (1)
    return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
  }

  void setRemarks(String remarks) {                                         (5)
    this.remarks = remarks;
  }
}
1identifier プロパティは final ですが、コンストラクターで null に設定されます。クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。元の Person インスタンスは、新しいインスタンスが作成されるときに変更されません。通常、ストア管理される他のプロパティにも同じパターンが適用されますが、永続化操作のために変更する必要がある場合があります。永続化コンストラクター(6 を参照)は事実上コピーコンストラクターであり、プロパティの設定は新しい識別子値が適用された新しいインスタンスの作成に変換されるため、wither メソッドはオプションです。
2firstname および lastname プロパティは、getter を介して潜在的に公開される通常の不変のプロパティです。
3age プロパティは不変ですが、birthday プロパティから派生しています。示されている設計では、Spring Data は宣言された唯一のコンストラクターを使用するため、データベース値はデフォルト設定よりも優先されます。計算が優先されることを意図している場合でも、このコンストラクターがパラメーターとして age を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは年齢フィールドを設定しようとし、不変で no with …  メソッドが存在します。
4comment プロパティは可変であり、フィールドを直接設定することで入力されます。
5remarks プロパティは可変であり、comment フィールドを直接設定するか、setter メソッドを呼び出して設定します。
6 このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの核となる考え方は、追加のコンストラクターの代わりにファクトリメソッドを使用して、@PersistenceConstructor によるコンストラクターの明確化の必要性を回避することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。

13.2.3. 一般的な推奨事項

  • 不変オブジェクトに固執しようとする — 不変オブジェクトは、オブジェクトを具体化するのはコンストラクターのみを呼び出すだけなので、簡単に作成できます。また、これにより、クライアントオブジェクトがオブジェクトの状態を操作できるようにする setter メソッドがドメインオブジェクトに散らばるのを防ぎます。それらが必要な場合は、同じ場所に配置された限られたタイプでのみ呼び出せるように、パッケージを保護することをお勧めします。コンストラクターのみの実体化は、プロパティの設定よりも最大 30% 高速です。

  • all-args コンストラクターを提供する  —  エンティティを不変の値としてモデル化できない、またはしたくない場合でも、オブジェクトのマッピングがプロパティの設定をスキップできるため、エンティティのすべてのプロパティを引数として取るコンストラクターを提供することには価値があります。最適なパフォーマンスのため。

  • @PersistenceConstructor を回避するために、オーバーロードされたコンストラクターの代わりにファクトリメソッドを使用します — 最適なパフォーマンスに必要なすべての引数コンストラクターでは、通常、自動生成識別子などを省略したアプリケーションユースケース固有のコンストラクターを公開します。これらの all-args コンストラクターのバリアントを公開する静的ファクトリメソッド。

  • 生成されたインスタンス生成クラスとプロパティアクセッサクラスを使用できるようにする制約を必ず守ってください。

  • 生成される識別子については、すべての引数の永続化コンストラクター(推奨)または with …  メソッドと組み合わせて final フィールドを使用します

  • Lombok を使用してボイラープレートコードを回避します — 永続化操作は通常、すべての引数を取るコンストラクターを必要とするため、その宣言はフィールド割り当てに対するボイラープレートパラメーターの退屈な繰り返しとなりますが、Lombok の @AllArgsConstructor を使用することで回避することができます。

13.2.4. Kotlin サポート

Spring Data は、Kotlin の仕様を適合させて、オブジェクトの作成と変更を可能にします。

Kotlin オブジェクトの作成

Kotlin クラスはインスタンス化がサポートされており、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の data クラス Person を検討してください。

data class Person(val id: String, val name: String)

上記のクラスは、明示的なコンストラクターを持つ典型的なクラスにコンパイルされます。別のコンストラクターを追加してこのクラスをカスタマイズし、@PersistenceConstructor でアノテーションを付けてコンストラクターの設定を示します。

data class Person(var id: String, val name: String) {

    @PersistenceConstructor
    constructor(id: String) : this(id, "unknown")
}

Kotlin は、パラメーターが提供されない場合にデフォルト値を使用できるようにすることで、パラメーターのオプションをサポートしています。Spring Data がパラメーターのデフォルト設定を持つコンストラクターを検出した場合、データストアが値を提供しない(または単に null を返す)場合、Kotlin はパラメーターのデフォルト設定を適用できるため、これらのパラメーターは存在しません。name のパラメーターのデフォルト設定を適用する次のクラスを検討してください。

data class Person(var id: String, val name: String = "unknown")

name パラメーターが結果の一部ではないか、その値が null であるたびに、name は unknown にデフォルト設定されます。

Kotlin データクラスのプロパティ設定

Kotlin では、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の data クラス Person を検討してください。

data class Person(val id: String, val name: String)

このクラスは事実上不変です。Kotlin が既存のオブジェクトからすべてのプロパティ値をコピーしてメソッドに引数として提供されたプロパティ値を適用する新しいオブジェクトインスタンスを作成する copy(…) メソッドを生成するときに、新しいインスタンスを作成できます。

13.3. オブジェクトからハッシュへのマッピング

Redis リポジトリのサポートは、オブジェクトをハッシュに永続化します。これには、RedisConverter によって実行されるオブジェクトからハッシュへの変換が必要です。デフォルトの実装では、Redis ネイティブ byte[] との間でプロパティ値をマッピングするために Converter を使用します。

前のセクションの Person タイプを考えると、デフォルトのマッピングは次のようになります。

_class = org.example.Person                 (1)
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand                            (2)
lastname = al ’ thor
address.city = emond's field                (3)
address.country = andor
1_class 属性は、ルートレベルだけでなく、ネストされたインターフェースまたは抽象型にも含まれています。
2 単純なプロパティ値はパスによってマップされます。
3 複合型のプロパティは、ドットパスによってマップされます。

次の表に、デフォルトのマッピングルールを示します。

表 10: デフォルトのマッピングルール
タイプ サンプル マップされた値

シンプルタイプ
(たとえば、文字列)

String firstname = "rand" ;

名 = " ランド "

複合型
(たとえば、住所)

住所 address = new Address("emond ’ s field");

address.city = " モンドのフィールド "

リスト
シンプルタイプ

List <String> nicknames = asList("dragon reborn"、"lews therin");

ニックネーム。[0] =「生まれ変わったドラゴン」、
ニックネーム。[1] = "lews therin "

地図
シンプルタイプ

Map <String、String> atts = asMap({"eye-color"、"grey" }、{" …

atts。[eye-color] =" grey "、
atts。[hair-color] =" …

リスト
複合型

リスト <Address> アドレス = asList(new Address( "em …

addresss。[0] .city =" emond's field "、
アドレス。[1] .city =" …

地図
複合型

Map <String、Address> addresss = asMap({"home"、new Address("em …

addresss。[home] .city = "emond's field "、
addresss。[work] .city =" …

フラットな表現構造のため、マップキーは String や Number などの単純なタイプである必要があります。

対応する Converter を RedisCustomConversions に登録することにより、マッピング動作をカスタマイズできます。これらのコンバーターは、Map<String,byte[]> だけでなく、単一の byte[] との間の変換も処理できます。1 つ目は、(たとえば)複合型を(たとえば)デフォルトのマッピングハッシュ構造を使用するバイナリ JSON 表現に変換するのに適しています。2 番目のオプションは、結果のハッシュを完全に制御します。

Redis ハッシュにオブジェクトを書き込むと、ハッシュからコンテンツが削除され、ハッシュ全体が再作成されるため、マップされていないデータは失われます。

次の例は、2 つのサンプルバイト配列コンバーターを示しています。

例 15: サンプル byte [] コンバーター
@WritingConverter
public class AddressToBytesConverter implements Converter<Address, byte[]> {

  private final Jackson2JsonRedisSerializer<Address> serializer;

  public AddressToBytesConverter() {

    serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
    serializer.setObjectMapper(new ObjectMapper());
  }

  @Override
  public byte[] convert(Address value) {
    return serializer.serialize(value);
  }
}

@ReadingConverter
public class BytesToAddressConverter implements Converter<byte[], Address> {

  private final Jackson2JsonRedisSerializer<Address> serializer;

  public BytesToAddressConverter() {

    serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
    serializer.setObjectMapper(new ObjectMapper());
  }

  @Override
  public Address convert(byte[] value) {
    return serializer.deserialize(value);
  }
}

上記のバイト配列 Converter を使用すると、次のような出力が生成されます。

_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al ’ thor
address = { city : "emond's field", country : "andor" }

次の例は、Map コンバーターの 2 つの例を示しています。

例 16: サンプル Map <String、byte []> コンバーター
@WritingConverter
public class AddressToMapConverter implements Converter<Address, Map<String,byte[]>> {

  @Override
  public Map<String,byte[]> convert(Address source) {
    return singletonMap("ciudad", source.getCity().getBytes());
  }
}

@ReadingConverter
public class MapToAddressConverter implements Converter<Map<String, byte[]>, Address> {

  @Override
  public Address convert(Map<String,byte[]> source) {
    return new Address(new String(source.get("ciudad")));
  }
}

上記のマップ Converter を使用すると、次のような出力が生成されます。

_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al ’ thor
ciudad = "emond's field"
カスタム変換は、インデックスの解決には影響しません。カスタム変換されたタイプの場合でも、二次インデックスは引き続き作成されます。

13.3.1. タイプマッピングのカスタマイズ

Java クラス名全体を型情報として記述したくない場合で、キーを使用したい場合は、永続化されるエンティティクラスで @TypeAlias アノテーションを使用できます。マッピングをさらにカスタマイズする必要がある場合は、TypeInformationMapper (Javadoc) インターフェースを確認してください。そのインターフェースのインスタンスは、MappingRedisConverter で構成できる DefaultRedisTypeMapper で構成できます。

次の例は、エンティティの型エイリアスを定義する方法を示しています。

例 17: エンティティの @TypeAlias の定義
@TypeAlias("pers")
class Person {

}

結果のドキュメントには、_class フィールドの値として pers が含まれています。

カスタムタイプマッピングの構成

次の例は、MappingRedisConverter でカスタム RedisTypeMapper を構成する方法を示しています。

例 18: Spring JavaConfig を介したカスタム RedisTypeMapper の構成
class CustomRedisTypeMapper extends DefaultRedisTypeMapper {
  //implement custom type mapping here
}
@Configuration
class SampleRedisConfiguration {

  @Bean
  public MappingRedisConverter redisConverter(RedisMappingContext mappingContext,
        RedisCustomConversions customConversions, ReferenceResolver referenceResolver) {

    MappingRedisConverter mappingRedisConverter = new MappingRedisConverter(mappingContext, null, referenceResolver,
            customTypeMapper());

    mappingRedisConverter.setCustomConversions(customConversions);

    return mappingRedisConverter;
  }

  @Bean
  public RedisTypeMapper customTypeMapper() {
    return new CustomRedisTypeMapper();
  }
}

13.4. キースペース

キースペースは、Redis ハッシュの実際のキーを作成するために使用されるプレフィックスを定義します。デフォルトでは、プレフィックスは getClass().getName() に設定されています。このデフォルトを変更するには、集約ルートレベルで @RedisHash を設定するか、プログラムによる構成を設定します。ただし、アノテーション付きのキースペースは他の構成よりも優先されます。

次の例は、@EnableRedisRepositories アノテーションを使用してキースペース構成を設定する方法を示しています。

例 19: @EnableRedisRepositories を介したキースペースのセットアップ
@Configuration
@EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

    @Override
    protected Iterable<KeyspaceSettings> initialConfiguration() {
      return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
    }
  }
}

次の例は、プログラムでキースペースを設定する方法を示しています。

例 20: プログラムによるキースペースの設定
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  @Bean
  public RedisMappingContext keyValueMappingContext() {
    return new RedisMappingContext(
      new MappingConfiguration(new IndexConfiguration(), new MyKeyspaceConfiguration()));
  }

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

    @Override
    protected Iterable<KeyspaceSettings> initialConfiguration() {
      return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
    }
  }
}

13.5. 二次インデックス

二次インデックス (英語) は、ネイティブ Redis 構造に基づくルックアップ操作を有効にするために使用されます。値は保存のたびに対応するインデックスに書き込まれ、オブジェクトが削除されるか期限切れになると削除されます。

13.5.1. シンプルプロパティインデックス

前に示したサンプル Person エンティティを前提として、次の例に示すように、プロパティに @Indexed アノテーションを付けることで、firstname のインデックスを作成できます。

例 21: アノテーション駆動型インデックス作成
@RedisHash("people")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address address;
}

インデックスは、実際のプロパティ値に対して作成されます。2 人(たとえば、「rand」と「aviendha」)を保存すると、次のようなインデックスが設定されます。

SADD people:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD people:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56

ネストされた要素にインデックスを付けることもできます。Address には、@Indexed でアノテーションが付けられた city プロパティがあると想定します。その場合、person.address.city が null でない場合、次の例に示すように、各都市のセットがあります。

SADD people:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e

さらに、プログラム設定では、次の例に示すように、マップキーとリストプロパティにインデックスを定義できます。

@RedisHash("people")
public class Person {

  // ... other properties omitted

  Map<String,String> attributes;      (1)
  Map<String Person> relatives;       (2)
  List<Address> addresses;            (3)
}
1SADD people:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e
2SADD people:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e
3SADD people:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
参照ではインデックスを解決できません。

キースペースと同様に、次の例に示すように、実際のドメインタイプにアノテーションを付けることなくインデックスを構成できます。

例 22: @EnableRedisRepositories を使用したインデックス設定
@Configuration
@EnableRedisRepositories(indexConfiguration = MyIndexConfiguration.class)
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  public static class MyIndexConfiguration extends IndexConfiguration {

    @Override
    protected Iterable<IndexDefinition> initialConfiguration() {
      return Collections.singleton(new SimpleIndexDefinition("people", "firstname"));
    }
  }
}

ここでも、キースペースと同様に、次の例に示すように、プログラムでインデックスを構成できます。

例 23: プログラマティックインデックスの設定
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  @Bean
  public RedisMappingContext keyValueMappingContext() {
    return new RedisMappingContext(
      new MappingConfiguration(
        new KeyspaceConfiguration(), new MyIndexConfiguration()));
  }

  public static class MyIndexConfiguration extends IndexConfiguration {

    @Override
    protected Iterable<IndexDefinition> initialConfiguration() {
      return Collections.singleton(new SimpleIndexDefinition("people", "firstname"));
    }
  }
}

13.5.2. 地理空間インデックス

Address タイプに、特定のアドレスの地理座標を保持するタイプ Point の location プロパティが含まれていると想定します。次の例に示すように、プロパティに @GeoIndexed アノテーションを付けることにより、Spring Data Redis は Redis GEO コマンドを使用してこれらの値を追加します。

@RedisHash("people")
public class Person {

  Address address;

  // ... other properties omitted
}

public class Address {

  @GeoIndexed Point location;

  // ... other properties omitted
}

public interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByAddressLocationNear(Point point, Distance distance);     (1)
  List<Person> findByAddressLocationWithin(Circle circle);                    (2)
}

Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address(new Point(13.361389D, 38.115556D)));

repository.save(rand);                                                        (3)

repository.findByAddressLocationNear(new Point(15D, 37D), new Distance(200)); (4)
1Point および Distance を使用して、ネストされたプロパティのメソッド宣言をクエリします。
2Circle を使用して内部を検索し、ネストされたプロパティのメソッド宣言をクエリします。
3GEOADD people:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e
4GEORADIUS people:address:location 15.0 37.0 200.0 km

前の例では、経度と緯度の値は、オブジェクトの id をメンバーの名前として使用する GEOADD を使用して保存されています。ファインダーメソッドを使用すると、Circle または Point, Distance の組み合わせを使用してこれらの値を照会できます。

near と within を他の条件と組み合わせることはできません。

13.6. 例示による問い合わせ

13.6.1. 導入

この章では、Query by Example の概要とその使用方法について説明します。

Query by Example(QBE)は、シンプルなインターフェースを備えた使いやすいクエリ手法です。動的なクエリの作成が可能になり、フィールド名を含むクエリを作成する必要がなくなります。実際、Query by Example では、ストア固有のクエリ言語を使用してクエリを記述する必要はまったくありません。

13.6.2. 使用方法

サンプル API によるクエリは、3 つの部分で構成されています。

  • プローブ: フィールドが設定されたドメインオブジェクトの実際の例。

  • ExampleMatcherExampleMatcher には、特定のフィールドの照合方法に関する詳細が記載されています。複数の例で再利用できます。

  • ExampleExample は、プローブと ExampleMatcher で構成されています。クエリの作成に使用されます。

例示による問い合わせは、いくつかのユースケースに適しています。

  • 静的または動的な制約のセットを使用してデータストアをクエリします。

  • 既存のクエリを壊すことを心配せずにドメインオブジェクトを頻繁にリファクタリングします。

  • 基礎となるデータストア API から独立して動作します。

例示による問い合わせには、いくつかの制限もあります。

  • firstname = ?0 or (firstname = ?1 and lastname = ?2) など、ネストまたはグループ化されたプロパティ制約はサポートされていません。

  • 文字列の starts/contains/ends/regex マッチングと他のプロパティタイプの完全一致のみをサポートします。

Query by Example を開始する前に、ドメインオブジェクトが必要です。開始するには、次の例に示すように、リポジトリのインターフェースを作成します。

例 24: サンプル Person オブジェクト
public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  //  …  getters and setters omitted
}

上記の例は、単純なドメインオブジェクトを示しています。これを使用して Example を作成できます。デフォルトでは、null 値を持つフィールドは無視され、ストア固有のデフォルトを使用して文字列が一致します。例は、of ファクトリメソッドを使用するか、ExampleMatcher を使用して作成できます。Example は不変です。次のリストは、簡単な例を示しています。

例 25: 簡単な例
Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)

Example<Person> example = Example.of(person);         (3)
1 ドメインオブジェクトの新しいインスタンスを作成します。
2 クエリにプロパティを設定します。
3Example を作成します。

リポジトリを使用して、サンプルクエリを実行できます。これを行うには、リポジトリインターフェースに QueryByExampleExecutor<T> を継承させます。次のリストは、QueryByExampleExecutor インターフェースからの抜粋を示しています。

例 26: QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {

  <S extends T> S findOne(Example<S> example);

  <S extends T> Iterable<S> findAll(Example<S> example);

  //  …  more functionality omitted.
}

13.6.3. マッチャーの例

例はデフォルト設定に限定されません。次の例に示すように、ExampleMatcher を使用して、文字列照合、null 処理、プロパティ固有の設定に独自のデフォルトを指定できます。

例 27: カスタマイズされたマッチングを使用したマッチャーの例
Person person = new Person();                          (1)
person.setFirstname("Dave");                           (2)

ExampleMatcher matcher = ExampleMatcher.matching()     (3)
  .withIgnorePaths("lastname")                         (4)
  .withIncludeNullValues()                             (5)
  .withStringMatcherEnding();                          (6)

Example<Person> example = Example.of(person, matcher); (7)
1 ドメインオブジェクトの新しいインスタンスを作成します。
2 セットのプロパティ。
3ExampleMatcher を作成して、すべての値が一致することを期待します。この段階では、さらに構成しなくても使用できます。
4lastname プロパティパスを無視する新しい ExampleMatcher を構築します。
5 新しい ExampleMatcher を作成して、lastname プロパティパスを無視し、null 値を含めます。
6 新しい ExampleMatcher を作成して、lastname プロパティパスを無視し、null 値を含め、サフィックス文字列の照合を実行します。
7 ドメインオブジェクトと設定された ExampleMatcher に基づいて新しい Example を作成します。

デフォルトでは、ExampleMatcher はプローブに設定されたすべての値が一致することを期待しています。暗黙的に定義された述語のいずれかに一致する結果を取得する場合は、ExampleMatcher.matchingAny() を使用します。

個々のプロパティ(「firstname」や「lastname」、ネストされたプロパティの場合は「address.city」など)の動作を指定できます。次の例に示すように、一致するオプションと大文字と小文字を区別して調整できます。

例 28: マッチャーオプションの構成
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

マッチャーオプションを構成する別の方法は、ラムダ(Java 8 で導入)を使用することです。このアプローチは、実装者にマッチャーの変更を要求するコールバックを作成します。設定オプションはマッチャーインスタンス内に保持されているため、マッチャーを返す必要はありません。次の例は、ラムダを使用するマッチャーを示しています。

例 29: ラムダを使用したマッチャーオプションの構成
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

Example によって作成されたクエリは、構成の統合ビューを使用します。デフォルトのマッチング設定は ExampleMatcher レベルで設定できますが、個々の設定は特定のプロパティパスに適用できます。ExampleMatcher で設定された設定は、明示的に定義されていない限り、プロパティパス設定に継承されます。プロパティパッチの設定は、デフォルト設定よりも優先されます。次の表は、さまざまな ExampleMatcher 設定の範囲を説明しています。

表 11: ExampleMatcher 設定の範囲
設定 スコープ

null ハンドリング

ExampleMatcher

文字列マッチング

ExampleMatcher とプロパティパス

プロパティを無視する

プロパティパス

大文字と小文字の区別

ExampleMatcher とプロパティパス

価値変革

プロパティパス

13.6.4. 例の実行

次の例では、リポジトリに対して例示による問い合わせを使用しています。

例 30: リポジトリを使用した例示による問い合わせ
interface PersonRepository extends QueryByExampleExecutor<Person> {
}

class PersonService {

  @Autowired PersonRepository personRepository;

  List<Person> findPeople(Person probe) {
    return personRepository.findAll(Example.of(probe));
  }
}

Redis リポジトリは、セカンダリインデックスとともに、Spring Data の Query byExample 機能のサブセットをサポートします。特に、クエリの作成には、正確で大文字と小文字が区別され、null 以外の値のみが使用されます。

セカンダリインデックスは、セットベースの操作(セット交差、セットユニオン)を使用して、一致するキーを決定します。インデックスが存在しないクエリにプロパティを追加しても、インデックスが存在しないため、結果は返されません。例示による問い合わせのサポートは、インデックス構成をインスペクションして、インデックスでカバーされるプロパティのみをクエリに含めます。これは、インデックス付けされていないプロパティが誤って含まれるのを防ぐためです。

大文字と小文字を区別しないクエリとサポートされていない StringMatcher インスタンスは、実行時に拒否されます。

次のリストは、サポートされている例示による問い合わせオプションを示しています。

  • 単純なプロパティとネストされたプロパティの大文字と小文字を区別した完全一致

  • 任意 / すべての一致モード

  • 条件値の値変換

  • 条件からの null 値の除外

次のリストは、Query byExample でサポートされていないプロパティを示しています。

  • 大文字と小文字を区別しないマッチング

  • 正規表現、プレフィックス / 含む / サフィックス文字列照合

  • 関連付け、コレクション、マップのようなプロパティのクエリ

  • 条件からの null 値の包含

  • ソート付き findAll 

13.7. 有効期間

Redis に保管されているオブジェクトは、一定期間のみ有効である可能性があります。これは、寿命が尽きたときに手動で削除しなくても、Redis で短命のオブジェクトを永続化する場合に特に便利です。秒単位の有効期限は、@RedisHash(timeToLive=…​) および KeyspaceSettings を使用して設定できます(キースペースを参照)。

数値プロパティまたはメソッドのいずれかで @TimeToLive アノテーションを使用することにより、より柔軟な有効期限を設定できます。ただし、同じクラス内のメソッドとプロパティの両方に @TimeToLive を適用しないでください。次の例は、プロパティとメソッドの @TimeToLive アノテーションを示しています。

例 31: 有効期限
public class TimeToLiveOnProperty {

  @Id
  private String id;

  @TimeToLive
  private Long expiration;
}

public class TimeToLiveOnMethod {

  @Id
  private String id;

  @TimeToLive
  public long getTimeToLive() {
  	return new Random().nextLong();
  }
}
プロパティに @TimeToLive で明示的にアノテーションを付けると、Redis から実際の TTL または PTTL 値が読み取られます。-1 は、オブジェクトに有効期限が関連付けられていないことを示します。

リポジトリの実装により、RedisMessageListenerContainer を介した Redis キースペース通知 (英語) へのサブスクリプションが保証されます。

有効期限が正の値に設定されると、対応する EXPIRE コマンドが実行されます。オリジナルを永続化することに加えて、ファントムコピーは Redis に永続化され、オリジナルのコピーの 5 分後に期限切れになるように設定されます。これは、リポジトリサポートが RedisKeyExpiredEvent を公開できるようにするために行われ、元の値がすでに削除されている場合でも、キーが期限切れになるたびに Spring の ApplicationEventPublisher に期限切れの値を保持します。有効期限イベントは、Spring Data Redis リポジトリを使用する接続されたすべてのアプリケーションで受信されます。

デフォルトでは、アプリケーションの初期化時にキーの有効期限リスナーは無効になっています。起動モードは、@EnableRedisRepositories または RedisKeyValueAdapter で調整して、アプリケーションで、または TTL を使用してエンティティを最初に挿入したときにリスナーを起動できます。可能な値については、EnableKeyspaceEvents (Javadoc) を参照してください。

RedisKeyExpiredEvent は、期限切れのドメインオブジェクトのコピーとキーを保持します。

有効期限イベントリスナーの起動を遅らせるか無効にすると、RedisKeyExpiredEvent の公開に影響します。無効にされたイベントリスナーは、有効期限イベントを公開しません。起動が遅れると、リスナーの初期化が遅れるため、イベントが失われる可能性があります。
キースペース通知メッセージリスナーは、Redis の notify-keyspace-events 設定がまだ設定されていない場合、変更します。既存の設定は上書きされないため、これらの設定を正しく設定する(または空のままにする)必要があります。AWS ElastiCache では CONFIG が無効になっており、リスナーを有効にするとエラーが発生することに注意してください。
Redis Pub/Sub メッセージは永続的ではありません。アプリケーションのダウン中にキーの有効期限が切れた場合、有効期限イベントは処理されません。これにより、有効期限が切れたオブジェクトへの参照を含むセカンダリインデックスが作成される可能性があります。
@EnableKeyspaceEvents(shadowCopy = OFF) は、ファントムコピーのストレージを無効にし、Redis 内のデータサイズを削減します。RedisKeyExpiredEvent には、期限切れのキーの id のみが含まれます。

13.8. 永続的な参照

プロパティを @Reference でマークすると、値をハッシュ自体にコピーする代わりに、単純なキー参照を格納できます。次の例に示すように、Redis からロードすると、参照は自動的に解決され、オブジェクトにマップされます。

例 32: サンプルプロパティリファレンス
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al ’ thor
mother = people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56      (1)
1 参照は、参照されるオブジェクトのキー全体(keyspace:id)を格納します。
参照オブジェクトは、参照オブジェクトが保存されるときに永続化されません。参照のみが保存されるため、参照オブジェクトの変更を個別に永続化する必要があります。参照される型のプロパティに設定されたインデックスは解決されません。

13.9. 部分的な更新の永続化

場合によっては、エンティティ内に新しい値を設定するためだけに、エンティティ全体をロードして書き換える必要はありません。最後のアクティブ時間のセッションタイムスタンプは、1 つのプロパティを変更するようなシナリオである可能性があります。PartialUpdate を使用すると、エンティティ自体とインデックス構造の両方の潜在的な有効期限の更新を処理しながら、既存のオブジェクトに対する set および delete アクションを定義できます。次の例は、部分的な更新を示しています。

例 33: サンプルの部分更新
PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class)
  .set("firstname", "mat")                                                           (1)
  .set("address.city", "emond's field")                                              (2)
  .del("age");                                                                       (3)

template.update(update);

update = new PartialUpdate<Person>("e2c7dcee", Person.class)
  .set("address", new Address("caemlyn", "andor"))                                   (4)
  .set("attributes", singletonMap("eye-color", "grey"));                             (5)

template.update(update);

update = new PartialUpdate<Person>("e2c7dcee", Person.class)
  .refreshTtl(true);                                                                 (6)
  .set("expiration", 1000);

template.update(update);
1 単純な firstname プロパティを mat に設定します。
2 オブジェクト全体を渡すことなく、単純な「address.city」プロパティを「emond ’ sfield」に設定します。カスタム変換が登録されている場合、これは機能しません。
3age プロパティを削除します。
4 複雑な address プロパティを設定します。
5 値のマップを設定します。これにより、既存のマップが削除され、値が指定されたものに置き換えられます。
6 有効期間を変更すると、サーバーの有効期限が自動的に更新されます。
複雑なオブジェクトやマップ(または他のコレクション)構造を更新するには、Redis とさらに対話して既存の値を決定する必要があります。つまり、エンティティ全体の書き換えが高速になる可能性があります。

13.10. クエリとクエリメソッド

次の例に示すように、クエリメソッドを使用すると、メソッド名から単純なファインダークエリを自動的に導出できます。

例 34: サンプルリポジトリファインダーメソッド
public interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByFirstname(String firstname);
}
ファインダーメソッドで使用されるプロパティがインデックス作成用に設定されていることを確認してください。
Redis リポジトリのクエリメソッドは、ページングを使用したエンティティおよびエンティティのコレクションのクエリのみをサポートします。

派生クエリメソッドを使用しても、実行するクエリをモデル化するのに必ずしも十分ではない場合があります。RedisCallback は、インデックス構造またはカスタムインデックスの実際のマッチングをより細かく制御できます。これを行うには、次の例に示すように、id 値の単一または Iterable セットを返す RedisCallback を提供します。

例 35: RedisCallback を使用したサンプルファインダー
String user = //...

List<RedisSession> sessionsByUser = template.find(new RedisCallback<Set<byte[]>>() {

  public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
    return connection
      .sMembers("sessions:securityContext.authentication.principal.username:" + user);
  }}, RedisSession.class);

次の表は、Redis でサポートされているキーワードの概要と、そのキーワードを含むメソッドが基本的に何に変換されるかを示しています。

表 12: メソッド名内でサポートされているキーワード
キーワード サンプル Redis スニペット

And

findByLastnameAndFirstname

SINTER …:firstname:rand …:lastname:al ’ thor

Or

findByLastnameOrFirstname

SUNION …:firstname:rand …:lastname:al ’ thor

Is, Equals

findByFirstnamefindByFirstnameIsfindByFirstnameEquals

SINTER …:firstname:rand

IsTrue

FindByAliveIsTrue

SINTER …:alive:1

IsFalse

findByAliveIsFalse

SINTER …:alive:0

Top,First

findFirst10ByFirstnamefindTop5ByFirstname

13.11. クラスター上で実行されている Redis リポジトリ

クラスター化された Redis 環境で Redis リポジトリサポートを使用できます。ConnectionFactory 構成の詳細については、「Redis クラスター」セクションを参照してください。それでも、デフォルトのキー配布はエンティティとセカンダリインデックスをクラスタ全体とそのスロット全体に分散させるため、いくつかの追加の構成を行う必要があります。

次の表は、クラスター上のデータの詳細を示しています(前の例に基づく)。

キー タイプ スロット ノード

人々:e2c7dcee-b8cd-4424-883e-736ce564363e

ハッシュの ID

15171

127.0.0.1:7381

人々:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56

ハッシュの ID

7373

127.0.0.1:7380

人々:名:ランド

インデックス

1700

127.0.0.1:7379

一部のコマンド(SINTER や SUNION など)は、関連するすべてのキーが同じスロットにマップされている場合にのみサーバー側で処理できます。それ以外の場合は、クライアント側で計算を行う必要があります。キースペースを 1 つのスロットに固定すると、Redis サーバー側の計算をすぐに利用できるため便利です。次の表は、実行するとどうなるかを示しています(スロット列とノード列のポート値の変更に注意してください)。

キー タイプ スロット ノード

{ 人 }:e2c7dcee-b8cd-4424-883e-736ce564363e

ハッシュの ID

2399

127.0.0.1:7379

{ 人 }:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56

ハッシュの ID

2399

127.0.0.1:7379

{ 人 }:名:ランド

インデックス

2399

127.0.0.1:7379

Redis クラスターを使用する場合は、@RedisHash("{yourkeyspace}") を使用してキースペースを定義し、特定のスロットに固定します。

13.12. CDI 統合

リポジトリインターフェースのインスタンスは通常、コンテナーによって作成されます。Spring Data を使用する場合は、Spring が最も自然な選択です。Spring は、Bean インスタンスを作成するための高度な機能を提供します。Spring Data Redis には、CDI 環境でリポジトリの抽象化を使用できるカスタム CDI 拡張機能が付属しています。拡張機能は JAR の一部であるため、拡張機能をアクティブにするには、Spring Data RedisJAR をクラスパスにドロップします。

次に、次の例に示すように、RedisConnectionFactory および RedisOperations の CDI プロデューサーを実装してインフラストラクチャをセットアップできます。

class RedisOperationsProducer {


  @Produces
  RedisConnectionFactory redisConnectionFactory() {

    JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration());
    jedisConnectionFactory.afterPropertiesSet();

    return jedisConnectionFactory;
  }

  void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {

    if (redisConnectionFactory instanceof DisposableBean) {
      ((DisposableBean) redisConnectionFactory).destroy();
    }
  }

  @Produces
  @ApplicationScoped
  RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    template.setConnectionFactory(redisConnectionFactory);
    template.afterPropertiesSet();

    return template;
  }

}

必要な設定は、JavaEE 環境によって異なります。

Spring Data Redis CDI 拡張機能は、使用可能なすべてのリポジトリを CDI Bean として取得し、リポジトリタイプの Bean がコンテナーによってリクエストされるたびに、Spring Data リポジトリのプロキシを作成します。次の例に示すように、Spring Data リポジトリのインスタンスを取得するには、@Injected プロパティを宣言する必要があります。

class RepositoryClient {

  @Inject
  PersonRepository repository;

  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}

Redis リポジトリには、RedisKeyValueAdapter および RedisKeyValueTemplate インスタンスが必要です。これらの Bean は、提供された Bean が見つからない場合、Spring DataCDI 拡張によって作成および管理されます。ただし、独自の Bean を提供して、RedisKeyValueAdapter および RedisKeyValueTemplate の特定のプロパティを構成することはできます。

13.13. Redis リポジトリの構造

ストア自体としての Redis は、非常に狭い低レベル API を提供し、セカンダリインデックスやクエリ操作などの高レベル関数をユーザーに任せます。

このセクションでは、潜在的なパフォーマンスへの影響をよりよく理解するために、リポジトリ抽象化によって発行されたコマンドのより詳細なビューを提供します。

すべての操作の開始点として、次のエンティティクラスを検討してください。

例 36: エンティティの例
@RedisHash("people")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address hometown;
}

public class Address {

  @GeoIndexed Point location;
}

13.13.1. 新規挿入

repository.save(new Person("rand", "al'thor"));
HMSET "people:19315449-cda2-4f5c-b696-9cb8018fa1f9" "_class" "Person" "id" "19315449-cda2-4f5c-b696-9cb8018fa1f9" "firstname" "rand" "lastname" "al'thor" (1)
SADD  "people" "19315449-cda2-4f5c-b696-9cb8018fa1f9"                           (2)
SADD  "people:firstname:rand" "19315449-cda2-4f5c-b696-9cb8018fa1f9"            (3)
SADD  "people:19315449-cda2-4f5c-b696-9cb8018fa1f9:idx" "people:firstname:rand" (4)
1 平坦化されたエントリをハッシュとして保存します。
2<1> で記述されたハッシュのキーを、同じキースペース内のエンティティのヘルパーインデックスに追加します。
3<2> で記述されたハッシュのキーを、プロパティ値を持つ名のセカンダリインデックスに追加します。
4<3> のインデックスをエントリのヘルパー構造のセットに追加して、削除 / 更新時にクリーンアップするインデックスを追跡します。

13.13.2. 既存のものを置き換える

repository.save(new Person("e82908cf-e7d3-47c2-9eec-b4e0967ad0c9", "Dragon Reborn", "al'thor"));
DEL       "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9"                           (1)
HMSET     "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" "_class" "Person" "id" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" "firstname" "Dragon Reborn" "lastname" "al'thor" (2)
SADD      "people" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9"                         (3)
SMEMBERS  "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx"                       (4)
TYPE      "people:firstname:rand"                                                 (5)
SREM      "people:firstname:rand" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9"          (6)
DEL       "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx"                       (7)
SADD      "people:firstname:Dragon Reborn" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (8)
SADD      "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" "people:firstname:Dragon Reborn" (9)
1 既存のハッシュを削除して、ハッシュキーの残りが存在しなくなる可能性を回避します。
2 平坦化されたエントリをハッシュとして保存します。
3<1> で記述されたハッシュのキーを、同じキースペース内のエンティティのヘルパーインデックスに追加します。
4 更新が必要になる可能性のある既存のインデックス構造を取得します。
5 インデックスが存在するかどうか、およびそのタイプ(text、geo、…)を確認してください。
6 潜在的に存在するキーをインデックスから削除します。
7 インデックス情報を保持しているヘルパーを削除します。
8<2> で追加されたハッシュのキーを、プロパティ値を持つ名のセカンダリインデックスに追加します。
9<6> のインデックスをエントリのヘルパー構造のセットに追加して、削除 / 更新時にクリーンアップするインデックスを追跡します。

13.13.3. 地理データを保存する

ジオインデックスは、通常のテキストベースのインデックスと同じルールに従いますが、ジオ構造を使用して値を格納します。ジオインデックス付きプロパティを使用するエンティティを保存すると、次のコマンドが実行されます。

GEOADD "people:hometown:location" "13.361389" "38.115556" "76900e94-b057-44bc-abcf-8126d51a621b"  (1)
SADD   "people:76900e94-b057-44bc-abcf-8126d51a621b:idx" "people:hometown:location"               (2)
1 保存したエントリのキーを geo インデックスに追加します。
2 インデックス構造を追跡します。

13.13.4. 単純なインデックスを使用して検索

repository.findByFirstname("egwene");
SINTER  "people:firstname:egwene"                     (1)
HGETALL "people:d70091b5-0b9a-4c0a-9551-519e61bc9ef3" (2)
HGETALL ...
1 セカンダリインデックスに含まれるキーをフェッチします。
2<1> から返された各キーを個別に取得します。

13.13.5. ジオインデックスを使用して検索

repository.findByHometownLocationNear(new Point(15, 37), new Distance(200, KILOMETERS));
GEORADIUS "people:hometown:location" "15.0" "37.0" "200.0" "km" (1)
HGETALL   "people:76900e94-b057-44bc-abcf-8126d51a621b"         (2)
HGETALL   ...
1 セカンダリインデックスに含まれるキーをフェッチします。
2<1> から返された各キーを個別に取得します。

付録

付録ドキュメントの構造

付録には、残りのリファレンスドキュメントの情報を補足するさまざまな追加の詳細が含まれています。

  • スキーマ」は、Spring Data Redis によって提供されるスキーマを定義します。

  • コマンドリファレンス」では、RedisTemplate でサポートされているコマンドについて詳しく説明しています。

付録 A: スキーマ

付録 B: コマンドリファレンス

サポートされているコマンド

表 13: RedisTemplate でサポートされる Redis コマンド
コマンド テンプレートのサポート

APPEND

X

AUTH

X

BGREWRITEAOF

X

BGSAVE

X

BITCOUNT

X

BITFIELD

X

BITOP

X

BLPOP

X

BRPOP

X

BRPOPLPUSH

X

CLIENT KILL

X

CLIENT GETNAME

X

CLIENT LIST

X

CLIENT SETNAME

X

CLUSTER SLOTS

-

COMMAND

-

COMMAND COUNT

-

COMMAND GETKEYS

-

COMMAND INFO

-

CONFIG GET

X

CONFIG RESETSTAT

X

CONFIG REWRITE

-

CONFIG SET

X

DBSIZE

X

DEBUG OBJECT

-

DEBUG SEGFAULT

-

DECR

X

DECRBY

X

DEL

X

DISCARD

X

DUMP

X

ECHO

X

EVAL

X

EVALSHA

X

EXEC

X

EXISTS

X

EXPIRE

X

EXPIREAT

X

FLUSHALL

X

FLUSHDB

X

GET

X

GETBIT

X

GETRANGE

X

GETSET

X

HDEL

X

HEXISTS

X

HGET

X

HGETALL

X

HINCRBY

X

HINCRBYFLOAT

X

HKEYS

X

HLEN

X

HMGET

X

HMSET

X

HSCAN

X

HSET

X

HSETNX

X

HVALS

X

INCR

X

INCRBY

X

INCRBYFLOAT

X

INFO

X

KEYS

X

LASTSAVE

X

LINDEX

X

LINSERT

X

LLEN

X

LPOP

X

LPUSH

X

LPUSHX

X

LRANGE

X

LREM

X

LSET

X

LTRIM

X

MGET

X

MIGRATE

-

MONITOR

-

MOVE

X

MSET

X

MSETNX

X

MULTI

X

OBJECT

-

PERSIST

X

PEXIPRE

X

PEXPIREAT

X

PFADD

X

PFCOUNT

X

PFMERGE

X

PING

X

PSETEX

X

PSUBSCRIBE

X

PTTL

X

PUBLISH

X

PUBSUB

-

PUBSUBSCRIBE

-

QUIT

X

RANDOMKEY

X

RENAME

X

RENAMENX

X

RESTORE

X

ROLE

-

RPOP

X

RPOPLPUSH

X

RPUSH

X

RPUSHX

X

SADD

X

SAVE

X

SCAN

X

SCARD

X

SCRIPT EXITS

X

SCRIPT FLUSH

X

SCRIPT KILL

X

SCRIPT LOAD

X

SDIFF

X

SDIFFSTORE

X

SELECT

X

SENTINEL FAILOVER

X

SENTINEL GET-MASTER-ADD-BY-NAME

-

SENTINEL MASTER

-

SENTINEL MASTERS

X

SENTINEL MONITOR

X

SENTINEL REMOVE

X

SENTINEL RESET

-

SENTINEL SET

-

SENTINEL SLAVES

X

SET

X

SETBIT

X

SETEX

X

SETNX

X

SETRANGE

X

SHUTDOWN

X

SINTER

X

SINTERSTORE

X

SISMEMBER

X

SLAVEOF

X

SLOWLOG

-

SMEMBERS

X

SMOVE

X

SORT

X

SPOP

X

SRANDMEMBER

X

SREM

X

SSCAN

X

STRLEN

X

SUBSCRIBE

X

SUNION

X

SUNIONSTORE

X

SYNC

-

TIME

X

TTL

X

TYPE

X

UNSUBSCRIBE

X

UNWATCH

X

WATCH

X

ZADD

X

ZCARD

X

ZCOUNT

X

ZINCRBY

X

ZINTERSTORE

X

ZLEXCOUNT

-

ZRANGE

X

ZRANGEBYLEX

-

ZREVRANGEBYLEX

-

ZRANGEBYSCORE

X

ZRANK

X

ZREM

X

ZREMRANGEBYLEX

-

ZREMRANGEBYRANK

X

ZREVRANGE

X

ZREVRANGEBYSCORE

X

ZREVRANK

X

ZSCAN

X

ZSCORE

X

ZUNINONSTORE

X