© 2011-2023 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. サンプルを試す

https://github.com/spring-projects/spring-data-examples/ (英語) にある専用の Spring Data サンプルリポジトリで、キー値ストアのさまざまなサンプルを見つけることができます。詳細については、ドキュメント、次のブログエントリ (英語) を参照してください。

3. 要件

Spring Data Redis バイナリには、JDK レベル 17 以降および Spring Framework 6.0.13 以降が必要です。

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

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

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

コミュニティフォーラム

Stack Overflow (英語) 上の 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. 依存関係

個々の 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>2022.0.11</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

現在のリリーストレインバージョンは 2022.0.11 です。トレインバージョンでは、パターン 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>

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

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

詳細については、Spring Boot のドキュメント ( "Spring Data Bom" で検索) を参照してください。

6.2. Spring Framework

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

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

7. 導入

7.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)サポートのリファレンスガイドです。

8. Spring Data のアップグレード

Spring Data の以前のバージョンからアップグレードする方法については、プロジェクトの wiki [GitHub] (英語) で説明されています。リリースノートセクション [GitHub] (英語) のリンクに従って、アップグレードするバージョンを見つけます。

バージョンアップ手順は、常にリリースノートの最初の項目です。複数のバージョンを飛び越えてバージョンアップする場合は、途中のバージョンのリリースノートも確認してください。

8.1. 次のステップ

アプリケーションのアップグレードを決定したら、ドキュメントの残りの部分で特定の機能に関する詳細情報を見つけることができます。このドキュメントの最後に、メジャーバージョンの移行に固有の移行ガイドがあります。

Spring Data のドキュメントはバージョン固有であり、そこに記載されている情報は、そのバージョンの最新の変更点を含んでいます。

9. なぜ Spring Data Redis ?

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

NoSQL [Wikipedia] (英語) ストレージシステムは、水平方向のスケーラビリティと速度に関して、従来の 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. 入門

作業環境をセットアップする簡単な方法は、Pleiades All in One (JDK, 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>3.0.11</version>
      </dependency>
    
    </dependencies>
  2. pom.xml の Spring のバージョンを次のように変更します

    <spring.framework.version>6.0.13</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/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 ドキュメントの専用セクションを参照してください。

RedisConnection クラスはスレッドセーフではありません。Lettuce の StatefulRedisConnection など、基礎となるネイティブ接続はスレッドセーフである可能性がありますが、Spring Data Redis の LettuceConnection クラス自体はスレッドセーフではありません。RedisConnection のインスタンスを複数のスレッド間で共有しないでください。これは、トランザクションまたはブロッキング Redis 操作およびコマンド ( BLPOP など) に特に当てはまります。たとえば、トランザクション操作やパイプライン操作では、RedisConnection は操作を正しく完了するために保護されていない変更可能な状態を保持するため、複数のスレッドで使用するのは安全ではありません。これは仕様によるものです。
パフォーマンス上の理由などにより、接続などの (ステートフル) Redis リソースを複数のスレッド間で共有する必要がある場合は、ネイティブ接続を取得し、Redis クライアントライブラリ (ドライバー) API を直接使用する必要があります。あるいは、スレッドセーフな方法で操作 (および Redis コマンド) のための接続を取得および管理する RedisTemplate を使用することもできます。詳細については、RedisTemplate のドキュメントを参照してください。
基盤となる構成に応じて、ファクトリは新しい接続または既存の接続を返すことができます(プールまたは共有ネイティブ接続が使用されている場合)。

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

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

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

スタンドアロン接続

マスター / レプリカ接続

Redis Sentinel

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

マスタールックアップ

Redis クラスター

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

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

輸送チャネル

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

TCP

接続プーリング

X (commons-pool2 を使用)

X (commons-pool2 を使用)

その他の接続機能

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

パイプライン処理とトランザクションは相互に排他的です。パイプライン / トランザクションでサーバー / 接続コマンドを使用することはできません。

SSL サポート

Pub/Sub

Pipelining

X (パイプライン処理とトランザクションは相互に排他的です)

Transactions

X (パイプライン処理とトランザクションは相互に排他的です)

データ型のサポート

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

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

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

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.2.6.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>4.3.2</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(REPLICA_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 (英語) をサポートしています。

/**
 * 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);
}

/**
 * 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);
}

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

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

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

  • spring.redis.sentinel.username: Redis で認証するときに適用するユーザー名 Sentinel (Redis 6 が必要です)

  • 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] 呼び出しを排除します。

Java
@Configuration
class MyConfig {

  @Bean
  LettuceConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

    RedisTemplate<String, String> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
}
XML
<?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="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
  <!-- redis template definition -->
  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>
  ...

</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 とコードの両方で同じエンコーディングが使用されていると仮定します)。次のリストは例を示しています。

Java
@Configuration
class MyConfig {

  @Bean
  LettuceConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
}
XML
<?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="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>

  <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>

</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 [Wikipedia] (英語) 形式でデータを格納するために Jackson2JsonRedisSerializer または GenericJackson2JsonRedisSerializer を使用することもできます。

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

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

Java シリアライゼーションによるセキュリティの脆弱性が懸念される場合は、コア JVM レベルでの汎用シリアライゼーションフィルターメカニズムを検討してください。

10.9. ハッシュマッピング

データは、Redis 内のさまざまなデータ構造を使用して保存できます。Jackson2JsonRedisSerializer は、オブジェクトを JSON [Wikipedia] (英語) 形式に変換できます。理想的には、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 = ...
Long numberOfClients = 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 インフラストラクチャの関心事をフレームワークに委譲できます。

MessageListener は、追加で SubscriptionListener を実装して、サブスクリプション / サブスクライブ解除の確認時に通知を受信できます。サブスクリプション通知をリッスンすることは、呼び出しを同期するときに役立ちます。

さらに、アプリケーションのフットプリントを最小限に抑えるために、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 です。

Java
@Configuration
class MyConfig {

  // …

  @Bean
  DefaultMessageDelegate listener() {
    return new DefaultMessageDelegate();
  }

  @Bean
  MessageListenerAdapter messageListenerAdapter(DefaultMessageDelegate listener) {
    return new MessageListenerAdapter(listener, "handleMessage");
  }

  @Bean
  RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory, MessageListenerAdapter listener) {

    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.addMessageListener(listener, ChannelTopic.of("chatroom"));
    return container;
  }
}
XML
<?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 ストリームのコア機能を提供します。

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: ストリームの直列化
ストリームプロパティ シリアライザー 説明

key

keySerializer

Record#getStream() に使用

field

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 は、multiexecdiscard コマンドを介したトランザクション (英語) のサポートを提供します。これらの操作は 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 メソッドがあります。

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;
  }
});

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

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

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

LettuceConnectionFactory factory = // ...
factory.setPipeliningFlushPolicy(PipeliningFlushPolicy.buffered(3)); (1)
1 ローカルでバッファリングし、3 番目のコマンドごとにフラッシュします。

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. Redis キャッシュ

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(connectionFactory))
	.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);

キャッシュの実装では、デフォルトで KEYS と DEL を使用してキャッシュをクリアします。KEYS は、大きなキースペースでパフォーマンスの課題を引き起こす可能性があります。デフォルトの RedisCacheWriter を BatchStrategy で作成して、SCAN ベースのバッチ戦略に切り替えることができます。SCAN 戦略では、過剰な Redis コマンドのラウンドトリップを回避するためにバッチサイズが必要です。

RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
	.cacheDefaults(defaultCacheConfig())
	...
KEYS バッチ戦略は、任意のドライバーと Redis 操作モード(スタンドアロン、クラスター化)を使用して完全にサポートされます。Lettuce ドライバーを使用する場合、SCAN は完全にサポートされます。Jedis は、非クラスターモードでのみ SCAN をサポートします。

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

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

キャッシュライター

ノンロック、KEYS バッチ戦略

キャッシュ構成

RedisCacheConfiguration#defaultConfiguration

初期キャッシュ

なし

トランザクション対応

いいえ

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

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

キャッシュ null

はい

プレフィックスキー

はい

デフォルト接頭部

実際のキャッシュ名

キーシリアライザー

StringRedisSerializer

バリューシリアライザー

JdkSerializationRedisSerializer

変換サービス

DefaultFormattingConversionService with default cache key converters

By default RedisCache, statistics are disabled. Use RedisCacheManagerBuilder.enableStatistics() to collect local hits and misses through RedisCache#getStatistics(), returning a snapshot of the collected data.

10.16. Support Classes

Package org.springframework.data.redis.support offers various reusable components that rely on Redis as a backing store. Currently, the package contains various JDK-based interface implementations on top of Redis, such as atomic (標準 Javadoc) (英語) counters and JDK Collections (標準 Javadoc) (英語) .

The atomic counters make it easy to wrap Redis key incrementation while the collections allow easy management of Redis keys with minimal storage exposure or API leakage. In particular, the RedisSet and RedisZSet interfaces offer easy access to the set operations supported by Redis, such as intersection and union. RedisList implements the List, Queue, and Deque contracts (and their equivalent blocking siblings) on top of Redis, exposing the storage as a FIFO (First-In-First-Out), LIFO (Last-In-First-Out) or capped collection with minimal configuration. The following example shows the configuration for a bean that uses a RedisList:

Java
@Configuration
class MyConfig {

  // …

  @Bean
  RedisList<String> stringRedisTemplate(RedisTemplate<String, String> redisTemplate) {
    return new DefaultRedisList<>(template, "queue-key");
  }
}
XML
<?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>

The following example shows a Java configuration example for a Deque:

public class AnotherExample {

  // injected
  private Deque<String> queue;

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

As shown in the preceding example, the consuming code is decoupled from the actual storage implementation. In fact, there is no indication that Redis is used underneath. This makes moving from development to production environments transparent and highly increases testability (the Redis implementation can be replaced with an in-memory one).

10.17. Observability

Getting insights from an application component about its operations, timing and relation to application code is crucial to understand latency. Spring Data Redis ships with a Micrometer integration through the Lettuce driver to collect observations during Redis interaction. Once the integration is set up, Micrometer will create meters and spans (for distributed tracing) for each Redis command.

To enable the integration, apply the following configuration to LettuceClientConfiguration:

@Configuration
class ObservabilityConfiguration {

  @Bean
  public ClientResources clientResources(ObservationRegistry observationRegistry) {

    return ClientResources.builder()
              .tracing(new MicrometerTracingAdapter(observationRegistry, "my-redis-cache"))
              .build();
  }

  @Bean
  public LettuceConnectionFactory lettuceConnectionFactory(ClientResources clientResources) {

    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                                                .clientResources(clientResources).build();
    RedisConfiguration redisConfiguration = …;
    return new LettuceConnectionFactory(redisConfiguration, clientConfig);
  }
}

10.17.1. Observability - Conventions

Below you can find a list of all GlobalObservabilityConventions and ObservabilityConventions declared by this project.

10.17.2. Observability - Metrics

Below you can find a list of all metrics declared by this project.

Redis Command Observation

Timer created around a Redis command execution.

Metric name spring.data.redis. Type timer and base unit seconds.

Fully qualified name of the enclosing class org.springframework.data.redis.connection.lettuce.observability.RedisObservation.

Table 9. Low cardinality Keys

Name

Description

db.operation

Redis コマンド値。

db.redis.database_index

Redis データベースインデックス。

db.system

データベースシステム。

db.user

Redis ユーザー。

net.peer.name

データベースホストの名前。

net.peer.port

論理 リモートポート番号。

net.sock.peer.addr

Mongo ピアアドレス。

net.sock.peer.port

Mongo ピアポート。

net.transport

ネットワーク転送。

表 10: カーディナリティの高いキー

名前

説明

db.statement

Redis ステートメント。

spring.data.redis.command.error

Redis エラーレスポンス。

10.17.3. 可観測性 - スパン

以下に、このプロジェクトで宣言されたすべてのスパンのリストを示します。

Redis コマンド観測スパン

Redis コマンドの実行時に作成されるタイマー。

スパン名  spring.data.redis.

外側のクラス org.springframework.data.redis.connection.lettuce.observability.RedisObservation の完全修飾名。

表 11: タグキー

名前

説明

db.operation

Redis コマンド値。

db.redis.database_index

Redis データベースインデックス。

db.statement

Redis ステートメント。

db.system

データベースシステム。

db.user

Redis ユーザー。

net.peer.name

データベースホストの名前。

net.peer.port

論理 リモートポート番号。

net.sock.peer.addr

Mongo ピアアドレス。

net.sock.peer.port

Mongo ピアポート。

net.transport

ネットワーク転送。

spring.data.redis.command.error

Redis エラーレスポンス。

詳細については、OpenTelemetry セマンティック規則 (英語) も参照してください。

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 コマンドリファレンス (英語) からのグループ化に続く)を提供します。

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

キー型の操作

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"));

適切なサブスクリプションを待って確認するには、Mono<Flux<ChannelMessage>> を返す receiveLater メソッドを使用できます。結果の Mono は、指定されたトピックへのサブスクリプションを完了した結果として、内部パブリッシャーで完了します。onNext シグナルをインターセプトすることで、サーバー側のサブスクリプションを同期できます。

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

Mono<Flux<ChannelMessage<String, String>>> stream = container.receiveLater(ChannelTopic.of("my-channel"));

stream.doOnNext(inner -> // notification hook when Redis subscriptions are synchronized with the server)
    .flatMapMany(Function.identity())
    .…;
テンプレート 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 クラスターとの通信を処理し、エラーを Spring DAO 例外階層に変換します。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 LettuceConnectionFactory(
            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:7381 MGETthing1 {thing1} .thing2
3 キーは異なるスロットにマップされ、対応するノードにルーティングされる単一のスロットに分割されます
→ 127.0.0.1:7379 GETthing2
→ 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 LettuceConnectionFactory();
  }

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

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    template.setConnectionFactory(redisConnectionFactory);
    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. @PersistenceCreator でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。

  2. コンストラクターが 1 つしかない場合は、それが使用されます。

  3. 複数のコンストラクターがあり、そのうちの 1 つだけに @PersistenceCreator アノテーションが付けられている場合は、それが使用されます。

  4. 型が Java Record の場合、標準コンストラクターが使用されます。

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

値の解決では、コンストラクター / ファクトリメソッドの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含め、プロパティが入力されたかのように解決が実行されます。これには、クラスファイルで利用可能なパラメーター名情報、またはコンストラクターに存在する @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 を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは age フィールドを設定しようとし、不変で no with …  メソッドが存在します。
4comment プロパティは変更可能で、そのフィールドを直接設定することによって入力されます。
5remarks プロパティは変更可能で、setter メソッドを呼び出すことによって設定されます。
6 このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの中心的な考え方は、@PersistenceCreator によるコンストラクターの曖昧性解消の必要性を回避するために、追加のコンストラクターの代わりにファクトリメソッドを使用することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。Spring Data でオブジェクトのインスタンス化にファクトリメソッドを使用する場合は、@PersistenceCreator でアノテーションを付けます。

13.2.3. 一般的な推奨事項

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

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

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

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

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

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

プロパティのオーバーライド

Java を使用すると、ドメインクラスを柔軟に設計できます。この場合、サブクラスは、スーパークラスで同じ名前ですでに宣言されているプロパティを定義できます。次の例を考えてみましょう。

public class SuperType {

   private CharSequence field;

   public SuperType(CharSequence field) {
      this.field = field;
   }

   public CharSequence getField() {
      return this.field;
   }

   public void setField(CharSequence field) {
      this.field = field;
   }
}

public class SubType extends SuperType {

   private String field;

   public SubType(String field) {
      super(field);
      this.field = field;
   }

   @Override
   public String getField() {
      return this.field;
   }

   public void setField(String field) {
      this.field = field;

      // optional
      super.setField(field);
   }
}

どちらのクラスも、割り当て可能な型を使用して field を定義します。ただし、SubType は SuperType.field をシャドウします。クラスの設計によっては、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチである可能性があります。または、setter で super.setField(…) を呼び出すと、SuperType で field を設定できます。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、これらすべてのメカニズムはある程度の競合を引き起こします。Spring Data は、型が割り当て可能でない場合、スーパー型のプロパティをスキップします。つまり、オーバーライドされたプロパティの型は、オーバーライドとして登録されるスーパー型のプロパティ型に割り当て可能である必要があります。そうでない場合、スーパー型のプロパティは一時的なものと見なされます。通常、個別のプロパティ名を使用することをお勧めします。

Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。

  1. どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに @Transient アノテーションを付けることで、プロパティを除外できます。

  2. データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。

  3. @AccessType(PROPERTY) を使用することは、通常、setter 実装のさらなる仮定を行わずにスーパープロパティを設定することができないため、使用できません。

13.2.4. Kotlin サポート

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

Kotlin オブジェクトの作成

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

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

  1. @PersistenceCreator でアノテーションが付けられたコンストラクターがある場合は、それが使用されます。

  2. 型が Kotlin データクラスの場合、プライマリコンストラクターが使用されます。

  3. @PersistenceCreator でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。

  4. コンストラクターが 1 つしかない場合は、それが使用されます。

  5. 複数のコンストラクターがあり、そのうちの 1 つだけに @PersistenceCreator アノテーションが付けられている場合は、それが使用されます。

  6. 型が Java Record の場合、標準コンストラクターが使用されます。

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

次の data クラス Person を検討してください。

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

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

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

    @PersistenceCreator
    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(…) メソッドを生成するときに、新しいインスタンスを作成できます。

Kotlin オーバーライドプロパティ

Kotlin では、プロパティのオーバーライド (英語) を宣言して、サブクラスのプロパティを変更できます。

open class SuperType(open var field: Int)

class SubType(override var field: Int = 1) :
	SuperType(field) {
}

このような配置では、field という名前の 2 つのプロパティがレンダリングされます。Kotlin は、各クラスの各プロパティのプロパティアクセサー(getter および setter)を生成します。事実上、コードは次のようになります。

public class SuperType {

   private int field;

   public SuperType(int field) {
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

public final class SubType extends SuperType {

   private int field;

   public SubType(int field) {
      super(field);
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

SubType の Getter および setter は、SubType.field のみを設定し、SuperType.field は設定しません。このような配置では、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチです。SubType にメソッドを追加して this.SuperType.field = …  を介して SuperType.field を設定することは可能ですが、サポートされている規則の範囲外です。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、プロパティのオーバーライドによってある程度の競合が発生します。通常、個別のプロパティ名を使用することをお勧めします。

Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。

  1. どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに @Transient アノテーションを付けることで、プロパティを除外できます。

  2. データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。

  3. @AccessType(PROPERTY) を使用すると、スーパープロパティが設定できないため使用できません。

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 複合型のプロパティは、ドットパスによってマップされます。

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

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

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

String firstname="rand" ;

名 = " ランド "

Byte 配列 (byte[])

byte[] image="rand" .getBytes();

image="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({" 目の色 ", " 灰色 "}, {" …

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

リスト
複合型

リスト <Address> アドレス = asList(新しいアドレス ("em …

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

地図
複合型

Map<String, Address> アドレス = 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 は、次の 4 つの部分で構成されています。

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

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

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

  • FetchableFluentQueryFetchableFluentQuery は流れるような API を提供し、Example から派生したクエリをさらにカスタマイズできるようにします。Fluent API を使用すると、クエリの順序付けの射影と結果の処理を指定できます。

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

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

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

  • 基礎となるデータストア 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 値を持つフィールドは無視され、文字列はストア固有のデフォルトを使用して照合されます。

例示による問い合わせ条件へのプロパティの包含は、null 可能性に基づいています。プリミティブ型(intdouble、…)を使用するプロパティは、ExampleMatcher はプロパティパスを無視しますでない限り、常に含まれます。

例は、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)
  .withStringMatcher(StringMatcher.ENDING);            (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() を使用します。

個々のプロパティ(「名」や「姓」、ネストされたプロパティの場合は "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 設定の範囲を説明しています。

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

null ハンドリング

ExampleMatcher

文字列マッチング

ExampleMatcher and property path

Ignoring properties

Property path

Case sensitivity

ExampleMatcher and property path

Value transformation

Property path

13.6.4. Fluent API

QueryByExampleExecutor offers one more method, which we did not mention so far: <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction). As with other methods, it executes a query derived from an Example. However, with the second argument, you can control aspects of that execution that you cannot dynamically control otherwise. You do so by invoking the various methods of the FetchableFluentQuery in the second argument. sortBy lets you specify an ordering for your result. as lets you specify the type to which you want the result to be transformed. project limits the queried attributes. first, firstValue, one, oneValue, all, page, stream, count, and exists define what kind of result you get and how the query behaves when more than the expected number of results are available.

Example 30. Use the fluent API to get the last of potentially many results, ordered by lastname.
Optional<Person> match = repository.findBy(example,
    q -> q
        .sortBy(Sort.by("lastname").descending())
        .first()
);

13.6.5. Running an Example

The following example uses Query by Example against a repository:

Example 31. Query by Example using a Repository
interface PersonRepository extends QueryByExampleExecutor<Person> {
}

class PersonService {

  @Autowired PersonRepository personRepository;

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

Redis Repositories support, with their secondary indexes, a subset of Spring Data’s Query by Example features. In particular, only exact, case-sensitive, and non-null values are used to construct a query.

Secondary indexes use set-based operations (Set intersection, Set union) to determine matching keys. Adding a property to the query that is not indexed returns no result, because no index exists. Query by Example support inspects indexing configuration to include only properties in the query that are covered by an index. This is to prevent accidental inclusion of non-indexed properties.

Case-insensitive queries and unsupported StringMatcher instances are rejected at runtime.

The following list shows the supported Query by Example options:

  • Case-sensitive, exact matching of simple and nested properties

  • Any/All match modes

  • Value transformation of the criteria value

  • Exclusion of null values from the criteria

The following list shows properties not supported by Query by Example:

  • Case-insensitive matching

  • Regex, prefix/contains/suffix String-matching

  • Querying of Associations, Collection, and Map-like properties

  • Inclusion of null values from the criteria

  • findAll with sorting

13.7. Time To Live

Objects stored in Redis may be valid only for a certain amount of time. This is especially useful for persisting short-lived objects in Redis without having to remove them manually when they reach their end of life. The expiration time in seconds can be set with @RedisHash(timeToLive=…​) as well as by using KeyspaceSettings (see Keyspaces).

More flexible expiration times can be set by using the @TimeToLive annotation on either a numeric property or a method. However, do not apply @TimeToLive on both a method and a property within the same class. The following example shows the @TimeToLive annotation on a property and on a method:

Example 32. Expirations
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();
  }
}
Annotating a property explicitly with @TimeToLive reads back the actual TTL or PTTL value from Redis. -1 indicates that the object has no associated expiration.

The repository implementation ensures subscription to Redis keyspace notifications (英語) via RedisMessageListenerContainer.

When the expiration is set to a positive value, the corresponding EXPIRE command is run. In addition to persisting the original, a phantom copy is persisted in Redis and set to expire five minutes after the original one. This is done to enable the Repository support to publish RedisKeyExpiredEvent, holding the expired value in Spring’s ApplicationEventPublisher whenever a key expires, even though the original values have already been removed. Expiry events are received on all connected applications that use Spring Data Redis repositories.

By default, the key expiry listener is disabled when initializing the application. The startup mode can be adjusted in @EnableRedisRepositories or RedisKeyValueAdapter to start the listener with the application or upon the first insert of an entity with a TTL. See EnableKeyspaceEvents (Javadoc) for possible values.

The RedisKeyExpiredEvent holds a copy of the expired domain object as well as the key.

Delaying or disabling the expiry event listener startup impacts RedisKeyExpiredEvent publishing. A disabled event listener does not publish expiry events. A delayed startup can cause loss of events because of the delayed listener initialization.
The keyspace notification message listener alters notify-keyspace-events settings in Redis, if those are not already set. Existing settings are not overridden, so you must set up those settings correctly (or leave them empty). Note that CONFIG is disabled on AWS ElastiCache, and enabling the listener leads to an error. To work around this behavior, set the keyspaceNotificationsConfigParameter parameter to an empty string. This prevents CONFIG command usage.
Redis Pub/Sub messages are not persistent. If a key expires while the application is down, the expiry event is not processed, which may lead to secondary indexes containing references to the expired object.
@EnableKeyspaceEvents(shadowCopy = OFF) disable storage of phantom copies and reduces data size within Redis. RedisKeyExpiredEvent will only contain the id of the expired key.

13.8. Persisting References

Marking properties with @Reference allows storing a simple key reference instead of copying values into the hash itself. On loading from Redis, references are resolved automatically and mapped back into the object, as shown in the following example:

Example 33. Sample Property Reference
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
mother = people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56      (1)
1 Reference stores the whole key (keyspace:id) of the referenced object.
Referenced Objects are not persisted when the referencing object is saved. You must persist changes on referenced objects separately, since only the reference is stored. Indexes set on properties of referenced types are not resolved.

13.9. Persisting Partial Updates

In some cases, you need not load and rewrite the entire entity just to set a new value within it. A session timestamp for the last active time might be such a scenario where you want to alter one property. PartialUpdate lets you define set and delete actions on existing objects while taking care of updating potential expiration times of both the entity itself and index structures. The following example shows a partial update:

Example 34. Sample Partial Update
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 Set the simple firstname property to mat.
2 Set the simple 'address.city' property to 'emond’s field' without having to pass in the entire object. This does not work when a custom conversion is registered.
3 Remove the age property.
4 Set complex address property.
5 Set a map of values, which removes the previously existing map and replaces the values with the given ones.
6 Automatically update the server expiration time when altering Time To Live.
Updating complex objects as well as map (or other collection) structures requires further interaction with Redis to determine existing values, which means that rewriting the entire entity might be faster.

13.10. Queries and Query Methods

Query methods allow automatic derivation of simple finder queries from the method name, as shown in the following example:

Example 35. Sample Repository finder Method
public interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByFirstname(String firstname);
}
Please make sure properties used in finder methods are set up for indexing.
Query methods for Redis repositories support only queries for entities and collections of entities with paging.

Using derived query methods might not always be sufficient to model the queries to run. RedisCallback offers more control over the actual matching of index structures or even custom indexes. To do so, provide a RedisCallback that returns a single or Iterable set of id values, as shown in the following example:

Example 36. Sample finder using 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);

The following table provides an overview of the keywords supported for Redis and what a method containing that keyword essentially translates to:

Table 15. Supported keywords inside method names
Keyword Sample Redis snippet

And

findByLastnameAndFirstname

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

Or

findByLastnameOrFirstname

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

Is, Equals

findByFirstname, findByFirstnameIs, findByFirstnameEquals

SINTER …:firstname:rand

IsTrue

FindByAliveIsTrue

SINTER …:alive:1

IsFalse

findByAliveIsFalse

SINTER …:alive:0

Top,First

findFirst10ByFirstname,findTop5ByFirstname

13.10.1. クエリメソッドの結果の並べ替え

Redis リポジトリでは、さまざまなアプローチでソート順を定義できます。Redis 自体は、ハッシュまたはセットを取得する際の実行中の並べ替えをサポートしていません。Redis リポジトリクエリメソッドは、結果を List として返す前に、結果に適用される Comparator を構築します。次の例を見てみましょう。

例 37: クエリ結果の並べ替え
interface PersonRepository extends RedisRepository<Person, String> {

  List<Person> findByFirstnameOrderByAgeDesc(String firstname); (1)

  List<Person> findByFirstname(String firstname, Sort sort);   (2)
}
1 メソッド名から派生した静的ソート。
2 メソッド引数を使用した動的ソート。

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 Redis JAR をクラスパスにドロップします。

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

class RedisOperationsProducer {


  @Produces
  RedisConnectionFactory redisConnectionFactory() {

    LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration());
    connectionFactory.afterPropertiesSet();

    return connectionFactory;
  }

  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 Data CDI 拡張によって作成および管理されます。ただし、独自の Bean を提供して、RedisKeyValueAdapter および RedisKeyValueTemplate の特定のプロパティを構成することはできます。

13.13. Redis リポジトリの構造

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

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

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

例 38: エンティティの例
@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 でサポートされているコマンドについて詳しく説明しています。

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

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

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

APPEND

AUTH

BGREWRITEAOF

BGSAVE

BITCOUNT

BITFIELD

BITOP

BLPOP

BRPOP

BRPOPLPUSH

CLIENT KILL

CLIENT GETNAME

CLIENT LIST

CLIENT SETNAME

CLUSTER SLOTS

-

COMMAND

-

COMMAND COUNT

-

COMMAND GETKEYS

-

COMMAND INFO

-

CONFIG GET

CONFIG RESETSTAT

CONFIG REWRITE

-

CONFIG SET

DBSIZE

DEBUG OBJECT

-

DEBUG SEGFAULT

-

DECR

DECRBY

DEL

DISCARD

DUMP

ECHO

EVAL

EVALSHA

EXEC

EXISTS

EXPIRE

EXPIREAT

FLUSHALL

FLUSHDB

GEOADD

GEODIST

GEOHASH

GEOPOS

GEORADIUS

GEORADIUSBYMEMBER

GEOSEARCH

GEOSEARCHSTORE

GET

GETBIT

GETRANGE

GETSET

HDEL

HEXISTS

HGET

HGETALL

HINCRBY

HINCRBYFLOAT

HKEYS

HLEN

HMGET

HMSET

HSCAN

HSET

HSETNX

HVALS

INCR

INCRBY

INCRBYFLOAT

INFO

KEYS

LASTSAVE

LINDEX

LINSERT

LLEN

LPOP

LPUSH

LPUSHX

LRANGE

LREM

LSET

LTRIM

MGET

MIGRATE

-

MONITOR

-

MOVE

MSET

MSETNX

MULTI

OBJECT

-

PERSIST

PEXIPRE

PEXPIREAT

PFADD

PFCOUNT

PFMERGE

PING

PSETEX

PSUBSCRIBE

PTTL

PUBLISH

PUBSUB

-

PUBSUBSCRIBE

-

QUIT

RANDOMKEY

RENAME

RENAMENX

REPLICAOF

RESTORE

ROLE

-

RPOP

RPOPLPUSH

RPUSH

RPUSHX

SADD

SAVE

SCAN

SCARD

SCRIPT EXITS

SCRIPT FLUSH

SCRIPT KILL

SCRIPT LOAD

SDIFF

SDIFFSTORE

SELECT

SENTINEL FAILOVER

SENTINEL GET-MASTER-ADD-BY-NAME

-

SENTINEL MASTER

-

SENTINEL MASTERS

SENTINEL MONITOR

SENTINEL REMOVE

SENTINEL RESET

-

SENTINEL SET

-

SENTINEL SLAVES

SET

SETBIT

SETEX

SETNX

SETRANGE

SHUTDOWN

SINTER

SINTERSTORE

SISMEMBER

SLAVEOF

SLOWLOG

-

SMEMBERS

SMOVE

SORT

SPOP

SRANDMEMBER

SREM

SSCAN

STRLEN

SUBSCRIBE

SUNION

SUNIONSTORE

SYNC

-

TIME

TTL

TYPE

UNSUBSCRIBE

UNWATCH

WATCH

ZADD

ZCARD

ZCOUNT

ZINCRBY

ZINTERSTORE

ZLEXCOUNT

-

ZRANGE

ZRANGEBYLEX

-

ZREVRANGEBYLEX

-

ZRANGEBYSCORE

ZRANGESTORE

ZRANK

ZREM

ZREMRANGEBYLEX

-

ZREMRANGEBYRANK

ZREVRANGE

ZREVRANGEBYSCORE

ZREVRANK

ZSCAN

ZSCORE

ZUNINONSTORE

付録 C: 移行ガイド

このセクションには、移行手順、非推奨、削除に関する詳細が含まれています。

2.x から 3.x へのアップグレード

削除 / 削除された型

タイプ 置換文字列

o.s.d.redis.Version

o.s.d.util.Version

o.s.d.redis.VersionParser

-

o.s.d.redis.connection.RedisZSetCommands.Aggregate

o.s.d.redis.connection.zset.Aggregate

o.s.d.redis.connection.RedisZSetCommands.Tuple

o.s.d.redis.connection.zset.Tuple

o.s.d.redis.connection.RedisZSetCommands.Weights

o.s.d.redis.connection.zset.Weights

o.s.d.redis.connection.RedisZSetCommands.Range

o.s.d.domain.Range

o.s.d.redis.connection.RedisZSetCommands.Limit

o.s.d.redis.connection.Limit.java

o.s.d.redis.connection.jedis.JedisUtils

-

o.s.d.redis.connection.jedis.JedisVersionUtil

-

o.s.d.redis.core.convert.CustomConversions

o.s.d.convert.CustomConversions

変更されたメソッドと型

表 17: コア
タイプ メソッド 置換文字列

o.s.d.redis.core.Cursor

開く

-

o.s.d.redis.core.RedisTemplate

実行する

doWithKeys

o.s.d.redis.stream.StreamMessageListenerContainer

isAutoAck

isAutoAcknowledge

o.s.d.redis.stream.StreamMessageListenerContainer

autoAck

autoAcknowledge

表 18: Redis 接続
タイプ メソッド 置換文字列

o.s.d.redis.connection.ClusterCommandExecutionFailureException

getCauses

getSuppressed

o.s.d.redis.connection.RedisConnection

bgWriteAof

bgReWriteAof

o.s.d.redis.connection.RedisConnection

slaveOf

replicaOf

o.s.d.redis.connection.RedisConnection

slaveOfNoOne

ReplicaOfNoOne

o.s.d.redis.connection.ReactiveClusterCommands

clusterGetSlaves

clusterGetReplicas

o.s.d.redis.connection.ReactiveClusterCommands

clusterGetMasterSlaveMap

clusterGetMasterReplicaMap

o.s.d.redis.connection.ReactiveKeyCommands

getNewName

getNewKey

o.s.d.redis.connection.RedisClusterNode.Flag

SLAVE

REPLICA

o.s.d.redis.connection.RedisClusterNode.Builder

slaveOf

replicaOf

o.s.d.redis.connection.RedisNode

isSlave

isReplica

o.s.d.redis.connection.RedisSentinelCommands

奴隷

レプリカ

o.s.d.redis.connection.RedisServer

getNumberSlaves

getNumberReplicas

o.s.d.redis.connection.RedisServerCommands

slaveOf

replicaOf

o.s.d.redis.core.ClusterOperations

getSlaves

getReplicas

o.s.d.redis.core.RedisOperations

slaveOf

replicaOf

表 19: Redis オペレーション
タイプ メソッド 置換文字列

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoAdd

追加

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoDist

距離

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoHash

ハッシュ

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoPos

位置

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoRadius

半径

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoRadiusByMember

半径

o.s.d.redis.core.GeoOperations & BoundGeoOperations

geoRemove

除去

表 20: Redis キャッシュ
タイプ メソッド 置換文字列

o.s.d.redis.cache.RedisCacheConfiguration

prefixKeysWith

prefixCacheNameWith

o.s.d.redis.cache.RedisCacheConfiguration

getKeyPrefix

getKeyPrefixFor

Jedis

重要なドライバーの変更について説明している Jedis アップグレードガイド [GitHub] (英語) を参照してください。

表 21: ジェダイ Redis 接続
タイプ メソッド 置換文字列

o.s.d.redis.connection.jedis.JedisConnectionFactory

getShardInfo

JedisClientConfiguration から入手できます

o.s.d.redis.connection.jedis.JedisConnectionFactory

setShardInfo

JedisClientConfiguration で設定できます

o.s.d.redis.connection.jedis.JedisConnectionFactory

createCluster

Jedis インスタンスの代わりに Connection が必要になりました

o.s.d.redis.connection.jedis.JedisConverters

パッケージの可視性があります

o.s.d.redis.connection.jedis.JedisConverters

tuplesToTuples

-

o.s.d.redis.connection.jedis.JedisConverters

tuplesToTuples

-

o.s.d.redis.connection.jedis.JedisConverters

stringListToByteList

-

o.s.d.redis.connection.jedis.JedisConverters

stringSetToByteSet

-

o.s.d.redis.connection.jedis.JedisConverters

stringMapToByteMap

-

o.s.d.redis.connection.jedis.JedisConverters

tupleSetToTupleSet

-

o.s.d.redis.connection.jedis.JedisConverters

toTupleSet

-

o.s.d.redis.connection.jedis.JedisConverters

toDataAccessException

o.s.d.redis.connection.jedis.JedisExceptionConverter#convert

トランザクション / パイプライン

パイプライン処理とトランザクションは相互に排他的になりました。パイプライン / トランザクションモードでサーバーまたは接続コマンドを使用することはできなくなりました。

Lettuce

Lettuce プール

LettucePool とその実装 DefaultLettucePool は、置き換えなしで削除されました。ドライバーのネイティブプーリング機能については、ドライバーのドキュメント (英語) を参照してください。プーリングパラメーターを受け入れるメソッドが更新されました。これは、LettuceConnectionFactory および LettuceConnection のメソッドに影響します。

Lettuce 認証

AuthenticatingRedisClient は交換せずに削除されました。認証データを設定するには、RedisURI のドライバのドキュメント (英語) を参照してください。