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

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

3. 要件

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

Key-Value ストアに関しては、Redis (英語) 2.6.x 以降が必要です。Spring Data Redis は現在、最新の 4.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. 注目の新機能

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

6.1. Spring Data の新機能 Redis 2.7

  • 番兵固有のユーザー名を考慮した Sentinel ACL 認証。ユーザー名を設定すると、Redis 6 を必要とするユーザー名とパスワードの認証が有効になります。

6.2. Spring Data の新機能 Redis 2.6

  • サブスクリプション確認コールバックに MessageListener を使用する場合の SubscriptionListener のサポート。ReactiveRedisMessageListenerContainer および ReactiveRedisOperations は、Redis がサブスクリプションを確認するまで待機する receiveLater(…) および listenToLater(…) メソッドを提供します。

  • Redis 6.2 コマンドをサポートします(LPOP/RPOP と countLMOVE/BLMOVECOPYGETEXGETDELGEOSEARCHGEOSEARCHSTOREZPOPMINBZPOPMINZPOPMAXBZPOPMAXZMSCOREZDIFFZDIFFSTOREZINTERZUNIONHRANDFIELDZRANDMEMBERSMISMEMBER)。

6.3. Spring Data の新機能 Redis 2.5

6.4. Spring Data の新機能 Redis 2.4

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

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

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

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

  • Jedis を使用したストリームコマンドのサポート。

6.5. Spring Data の新機能 Redis 2.3

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

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

6.6. Spring Data の新機能 Redis 2.2

  • Redis ストリーム

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

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

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

6.7. Spring Data の新機能 Redis 2.1

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

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

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

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

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

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

  • BITFIELDBITPOSOBJECT コマンドのサポート。

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

  • リアクティブ SCANHSCANSSCANZSCAN サポート。

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

6.8. Spring Data の新機能 Redis 2.0

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

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

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

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

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

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

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

  • Redis 3.2 の count コマンドで SPOP を追加します。

6.9. Spring Data の新機能 Redis 1.8

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

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

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

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

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

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

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

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

6.10. Spring Data の新機能 Redis 1.7

6.11. Spring Data の新機能 Redis 1.6

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

  • ZRANGEBYLEX のサポート。

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

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

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

6.12. Spring Data の新機能 Redis 1.5

  • Redis のサポートを追加します。HyperLogLog コマンド: PFADDPFCOUNTPFMERGE

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

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

7. 依存関係

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

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

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

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

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

  • RC1RC2 など: リリース候補

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

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

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

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

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

7.2. Spring Framework

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

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

8. 導入

8.1. ドキュメント構造

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

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

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

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

9. なぜ Spring Data Redis ?

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

NoSQL [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>2.7.17</version>
      </dependency>
    
    </dependencies>
  2. pom.xml の Spring のバージョンを次のように変更します

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

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

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 を使用)

その他の接続機能

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

JedisShardInfo support

SSL Support

X

X

Pub/Sub

X

X

Pipelining

X

X

Transactions

X

X

Datatype support

Key, String, List, Set, Sorted Set, Hash, Server, Stream, Scripting, Geo, HyperLogLog

Key, String, List, Set, Sorted Set, Hash, Server, Scripting, Geo, HyperLogLog

Reactive (non-blocking) API

X

10.4.2. Configuring the Lettuce Connector

Lettuce [GitHub] (英語) is a Netty (英語) -based open-source connector supported by Spring Data Redis through the org.springframework.data.redis.connection.lettuce package.

Add the following to the pom.xml files dependencies element:
<dependencies>

  <!-- other dependency elements omitted -->

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

</dependencies>

The following example shows how to create a new Lettuce connection factory:

@Configuration
class AppConfig {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

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

There are also a few Lettuce-specific connection parameters that can be tweaked. By default, all LettuceConnection instances created by the LettuceConnectionFactory share the same thread-safe native connection for all non-blocking and non-transactional operations. To use a dedicated connection each time, set shareNativeConnection to false. LettuceConnectionFactory can also be configured to use a LettucePool for pooling blocking and transactional connections or all connections if shareNativeConnection is set to false.

Lettuce integrates with Netty’s native transports (英語) , letting you use Unix domain sockets to communicate with Redis. Make sure to include the appropriate native transport dependencies that match your runtime environment. The following example shows how to create a Lettuce Connection factory for a Unix domain socket at /var/run/redis.sock:

@Configuration
class AppConfig {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
  }
}
Netty currently supports the epoll (Linux) and kqueue (BSD/macOS) interfaces for OS-native transport.

10.4.3. Configuring the Jedis Connector

Jedis [GitHub] (英語) is a community-driven connector supported by the Spring Data Redis module through the org.springframework.data.redis.connection.jedis package.

Add the following to the pom.xml files dependencies element:
<dependencies>

  <!-- other dependency elements omitted -->

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

</dependencies>

In its simplest form, the Jedis configuration looks as follow:

@Configuration
class AppConfig {

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

For production use, however, you might want to tweak settings such as the host or password, as shown in the following example:

@Configuration
class RedisConfiguration {

  @Bean
  public JedisConnectionFactory redisConnectionFactory() {

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

10.4.4. Write to Master, Read from Replica

The Redis Master/Replica setup — without automatic failover (for automatic failover see: Sentinel) — not only allows data to be safely stored at more nodes. It also allows, by using Lettuce, reading data from replicas while pushing writes to the master. You can set the read/write strategy to be used by using LettuceClientConfiguration, as shown in the following example:

@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);
  }
}
For environments reporting non-public addresses through the INFO command (for example, when using AWS), use RedisStaticMasterReplicaConfiguration instead of RedisStandaloneConfiguration. Please note that RedisStaticMasterReplicaConfiguration does not support Pub/Sub because of missing Pub/Sub message propagation across individual servers.

10.5. Redis Sentinel Support

For dealing with high-availability Redis, Spring Data Redis has support for Redis Sentinel (英語) , using RedisSentinelConfiguration, as shown in the following example:

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

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

RedisSentinelConfiguration can also be defined with a PropertySource, which lets you set the following properties:

Configuration Properties
  • spring.redis.sentinel.master: name of the master node.

  • spring.redis.sentinel.nodes: Comma delimited list of host:port pairs.

  • spring.redis.sentinel.username: The username to apply when authenticating with Redis Sentinel (requires Redis 6)

  • spring.redis.sentinel.password: The password to apply when authenticating with Redis Sentinel

Sometimes, direct interaction with one of the Sentinels is required. Using RedisConnectionFactory.getSentinelConnection() or RedisConnection.getSentinelCommands() gives you access to the first active Sentinel configured.

10.6. Working with Objects through RedisTemplate

Most users are likely to use RedisTemplate and its corresponding package, org.springframework.data.redis.core. The template is, in fact, the central class of the Redis module, due to its rich feature set. The template offers a high-level abstraction for Redis interactions. While RedisConnection offers low-level methods that accept and return binary values (byte arrays), the template takes care of serialization and connection management, freeing the user from dealing with such details.

Moreover, the template provides operations views (following the grouping from the Redis command reference (英語) ) that offer rich, generified interfaces for working against a certain type or certain key (through the KeyBound interfaces) as described in the following table:

Table 2. Operational views
Interface Description

Key Type Operations

GeoOperations

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

HashOperations

Redis ハッシュ演算

HyperLogLogOperations

Redis PFADDPFCOUNT、…などの HyperLogLog 操作

ListOperations

Redis リスト操作

SetOperations

Redis セット操作

ValueOperations

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

ZSetOperations

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

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

BoundGeoOperations

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

BoundHashOperations

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

BoundKeyOperations

Redis キーバインド操作

BoundListOperations

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

BoundSetOperations

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

BoundValueOperations

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

BoundZSetOperations

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

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

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

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

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

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

</beans>
public class Example {

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

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

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

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

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

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

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

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

  @Autowired
  private StringRedisTemplate redisTemplate;

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

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

public void useCallback() {

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

10.8. シリアライザー

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

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

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

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

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

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

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

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

  • StringRedisSerializer

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

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

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

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

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 = ...
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 です。

<?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 メソッドがあります。

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

10.12.1. @Transactional サポート

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

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

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

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

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

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

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

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

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

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

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

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

10.13. パイプライン

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

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

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

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

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

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

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

10.14. Redis スクリプティング

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

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

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

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

@Bean
public RedisScript<Boolean> script() {

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

  @Autowired
  RedisScript<Boolean> script;

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

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

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

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

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

10.15. 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:

<?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).

11. Reactive Redis support

This section covers reactive Redis support and how to get started. Reactive Redis support naturally has certain overlaps with imperative Redis support.

11.1. Redis Requirements

Spring Data Redis currently integrates with Lettuce [GitHub] (英語) as the only reactive Java connector. Project Reactor (英語) is used as reactive composition library.

11.2. Connecting to Redis by Using a Reactive Driver

One of the first tasks when using Redis and Spring is to connect to the store through the IoC container. To do that, a Java connector (or binding) is required. No matter the library you choose, you must use the org.springframework.data.redis.connection package and its ReactiveRedisConnection and ReactiveRedisConnectionFactory interfaces to work with and retrieve active connections to Redis.

11.2.1. Redis Operation Modes

Redis can be run as a standalone server, with Redis Sentinel, or in Redis Cluster mode. Lettuce [GitHub] (英語) supports all of the previously mentioned connection types.

11.2.2. ReactiveRedisConnection and ReactiveRedisConnectionFactory

ReactiveRedisConnection is the core of Redis communication, as it handles the communication with the Redis back-end. It also automatically translates the underlying driver exceptions to Spring’s consistent DAO exception hierarchy, so you can switch the connectors without any code changes, as the operation semantics remain the same.

ReactiveRedisConnectionFactory creates active ReactiveRedisConnection instances. In addition, the factories act as PersistenceExceptionTranslator instances, meaning that, once declared, they let you do transparent exception translation — for example, exception translation through the use of the @Repository annotation and AOP. For more information, see the dedicated section in the Spring Framework documentation.

Depending on the underlying configuration, the factory can return a new connection or an existing connection (in case a pool or shared native connection is used).
The easiest way to work with a ReactiveRedisConnectionFactory is to configure the appropriate connector through the IoC container and inject it into the using class.

11.2.3. Configuring a Lettuce Connector

Lettuce [GitHub] (英語) is supported by Spring Data Redis through the org.springframework.data.redis.connection.lettuce package.

You can set up ReactiveRedisConnectionFactory for Lettuce as follows:

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

The following example shows a more sophisticated configuration, including SSL and timeouts, that uses LettuceClientConfigurationBuilder:

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

For more detailed client configuration tweaks, see LettuceClientConfiguration (Javadoc) .

11.3. Working with Objects through ReactiveRedisTemplate

Most users are likely to use ReactiveRedisTemplate and its corresponding package, org.springframework.data.redis.core. Due to its rich feature set, the template is, in fact, the central class of the Redis module. The template offers a high-level abstraction for Redis interactions. While ReactiveRedisConnection offers low-level methods that accept and return binary values (ByteBuffer), the template takes care of serialization and connection management, freeing you from dealing with such details.

Moreover, the template provides operation views (following the grouping from Redis command reference (英語) ) that offer rich, generified interfaces for working against a certain type as described in the following table:

Table 9. Operational views
Interface Description

Key Type Operations

ReactiveGeoOperations

Redis geospatial operations such as GEOADD, GEORADIUS, and others)

ReactiveHashOperations

Redis hash operations

ReactiveHyperLogLogOperations

Redis HyperLogLog operations such as (PFADD, PFCOUNT, and others)

ReactiveListOperations

Redis list operations

ReactiveSetOperations

Redis set operations

ReactiveValueOperations

Redis string (or value) operations

ReactiveZSetOperations

Redis zset (or sorted set) operations

Once configured, the template is thread-safe and can be reused across multiple instances.

ReactiveRedisTemplate uses a Java-based serializer for most of its operations. This means that any object written or read by the template is serialized or deserialized through RedisElementWriter or RedisElementReader. The serialization context is passed to the template upon construction, and the Redis module offers several implementations available in the org.springframework.data.redis.serializer package. See Serializers for more information.

The following example shows a ReactiveRedisTemplate being used to return a Mono:

@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. String-focused Convenience Classes

Since it is quite common for keys and values stored in Redis to be a java.lang.String, the Redis module provides a String-based extension to ReactiveRedisTemplate: ReactiveStringRedisTemplate. It is a convenient one-stop solution for intensive String operations. In addition to being bound to String keys, the template uses the String-based RedisSerializationContext, which means the stored keys and values are human readable (assuming the same encoding is used in both Redis and your code). The following example shows ReactiveStringRedisTemplate in use:

@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 Messaging/PubSub

Spring Data provides dedicated messaging integration for Redis, very similar in functionality and naming to the JMS integration in Spring Framework; in fact, users familiar with the JMS support in Spring should feel right at home.

Redis messaging can be roughly divided into two areas of functionality, namely the production or publication and consumption or subscription of messages, hence the shortcut pubsub (Publish/Subscribe). The ReactiveRedisTemplate class is used for message production. For asynchronous reception, Spring Data provides a dedicated message listener container that is used consume a stream of messages. For the purpose of just subscribing ReactiveRedisTemplate offers stripped down alternatives to utilizing a listener container.

The package org.springframework.data.redis.connection and org.springframework.data.redis.listener provide the core functionality for using Redis messaging.

11.5.1. Sending/Publishing messages

To publish a message, one can use, as with the other operations, either the low-level ReactiveRedisConnection or the high-level ReactiveRedisTemplate. Both entities offer a publish method that accepts as an argument the message that needs to be sent as well as the destination channel. While ReactiveRedisConnection requires raw-data, the ReactiveRedisTemplate allow arbitrary objects to be passed in as messages:

// 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. Receiving/Subscribing for messages

On the receiving side, one can subscribe to one or multiple channels either by naming them directly or by using pattern matching. The latter approach is quite useful as it not only allows multiple subscriptions to be created with one command but to also listen on channels not yet created at subscription time (as long as they match the pattern).

At the low-level, ReactiveRedisConnection offers subscribe and pSubscribe methods that map the Redis commands for subscribing by channel respectively by pattern. Note that multiple channels or patterns can be used as arguments. To change a subscription, simply query the channels and patterns of ReactiveSubscription.

Reactive subscription commands in Spring Data Redis are non-blocking and may end without emitting an element.

As mentioned above, once subscribed a connection starts waiting for messages. No other commands can be invoked on it except for adding new subscriptions or modifying/canceling the existing ones. Commands other than subscribe, pSubscribe, unsubscribe, or pUnsubscribe are illegal and will cause an exception.

In order to receive messages, one needs to obtain the message stream. Note that a subscription only publishes messages for channels and patterns that are registered with that particular subscription. The message stream itself is a hot sequence that produces elements without regard to demand. Make sure to register sufficient demand to not exhaust the message buffer.

Message Listener Containers

Spring Data offers ReactiveRedisMessageListenerContainer which does all the heavy lifting of conversion and subscription state management on behalf of the user.

ReactiveRedisMessageListenerContainer acts as a message listener container. It is used to receive messages from a Redis channel and expose a stream of messages that emits channel messages with deserialization applied. It takes care of registering to receive messages, resource acquisition and release, exception conversion and the like. This allows you as an application developer to write the (possibly complex) business logic associated with receiving a message (and reacting to it), and delegates boilerplate Redis infrastructure concerns to the framework. Message streams register a subscription in Redis upon publisher subscription and unregister if the subscription gets canceled.

Furthermore, to minimize the application footprint, ReactiveRedisMessageListenerContainer allows one connection and one thread to be shared by multiple listeners even though they do not share a subscription. Thus no matter how many listeners or channels an application tracks, the runtime cost will remain the same through out its lifetime. Moreover, the container allows runtime configuration changes so one can add or remove listeners while an application is running without the need for restart. Additionally, the container uses a lazy subscription approach, using a ReactiveRedisConnection only when needed - if all the listeners are unsubscribed, cleanup is automatically performed.

The message listener container itself does not require external threading resources. It uses the driver threads to publish messages.

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

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

To await and ensure proper subscription, you can use the receiveLater method that returns a Mono<Flux<ChannelMessage>>. The resulting Mono completes with an inner publisher as a result of completing the subscription to the given topics. By intercepting onNext signals, you can synchronize server-side subscriptions.

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())
    .…;
Subscribing via template API

As mentioned above you can directly use ReactiveRedisTemplate to subscribe to channels / patterns. This approach offers a straight forward, though limited solution as you lose the option to add subscriptions after the initial ones. Nevertheless you still can control the message stream via the returned Flux using eg. take(Duration). When done reading, on error or cancellation all bound resources are freed again.

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

11.6. Reactive Scripting

You can run Redis scripts with the reactive infrastructure by using the ReactiveScriptExecutor, which is best accessed through ReactiveRedisTemplate.

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

See to the scripting section for more details on scripting commands.

12. Redis Cluster

Working with Redis Cluster (英語) requires Redis Server version 3.0+. See the Cluster Tutorial (英語) for more information.

12.1. Enabling Redis Cluster

Cluster support is based on the same building blocks as non-clustered communication. RedisClusterConnection, an extension to RedisConnection, handles the communication with the Redis Cluster and translates errors into the Spring DAO exception hierarchy. RedisClusterConnection instances are created with the RedisConnectionFactory, which has to be set up with the associated RedisClusterConfiguration, as shown in the following example:

Example 5. Sample RedisConnectionFactory Configuration for Redis Cluster
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {

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

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

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

@Configuration
public class AppConfig {

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

    public @Bean RedisConnectionFactory connectionFactory() {

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

RedisClusterConfiguration can also be defined through PropertySource and has the following properties:

Configuration Properties
  • spring.redis.cluster.nodes: Comma-delimited list of host:port pairs.

  • spring.redis.cluster.max-redirects: Number of allowed cluster redirections.

The initial configuration points driver libraries to an initial set of cluster nodes. Changes resulting from live cluster reconfiguration are kept only in the native driver and are not written back to the configuration.

12.2. Working With Redis Cluster Connection

As mentioned earlier, Redis Cluster behaves differently from single-node Redis or even a Sentinel-monitored master-replica environment. This is because the automatic sharding maps a key to one of 16384 slots, which are distributed across the nodes. Therefore, commands that involve more than one key must assert all keys map to the exact same slot to avoid cross-slot errors. A single cluster node serves only a dedicated set of keys. Commands issued against one particular server return results only for those keys served by that server. As a simple example, consider the KEYS command. When issued to a server in a cluster environment, it returns only the keys served by the node the request is sent to and not necessarily all keys within the cluster. So, to get all keys in a cluster environment, you must read the keys from all the known master nodes.

While redirects for specific keys to the corresponding slot-serving node are handled by the driver libraries, higher-level functions, such as collecting information across nodes or sending commands to all nodes in the cluster, are covered by RedisClusterConnection. Picking up the keys example from earlier, this means that the keys(pattern) method picks up every master node in the cluster and simultaneously runs the KEYS command on every master node while picking up the results and returning the cumulated set of keys. To just request the keys of a single node RedisClusterConnection provides overloads for those methods (for example, keys(node, pattern)).

A RedisClusterNode can be obtained from RedisClusterConnection.clusterGetNodes or it can be constructed by using either the host and the port or the node Id.

The following example shows a set of commands being run across the cluster:

Example 6. Sample of Running Commands Across the Cluster
[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 Master node serving slots 0 to 5460 replicated to replica at 7382
2 Master node serving slots 5461 to 10922
3 Master node serving slots 10923 to 16383
4 Replica node holding replicants of the master at 7379
5 Request routed to node at 7381 serving slot 12182
6 Request routed to node at 7379 serving slot 5061
7 Request routed to nodes at 7379, 7380, 7381 → [thing1, thing2]
8 Request routed to node at 7379 → [thing2]
9 Request routed to node at 7380 → []
10 Request routed to node at 7381 → [thing1]
11 Request routed to node at 7382 → [thing2]

When all keys map to the same slot, the native driver library automatically serves cross-slot requests, such as MGET. However, once this is not the case, RedisClusterConnection runs multiple parallel GET commands against the slot-serving nodes and again returns an accumulated result. This is less performant than the single-slot approach and, therefore, should be used with care. If in doubt, consider pinning keys to the same slot by providing a prefix in curly brackets, such as {my-prefix}.thing1 and {my-prefix}.thing2, which will both map to the same slot number. The following example shows cross-slot request handling:

Example 7. Sample of Cross-Slot Request Handling
[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 Same Configuration as in the sample before.
2 Keys map to same slot → 127.0.0.1:7381 MGET thing1 {thing1}.thing2
3 Keys map to different slots and get split up into single slot ones routed to the according nodes
→ 127.0.0.1:7379 GET thing2
→ 127.0.0.1:7381 GET thing1
The preceding examples demonstrate the general strategy followed by Spring Data Redis. Be aware that some operations might require loading huge amounts of data into memory to compute the desired command. Additionally, not all cross-slot requests can safely be ported to multiple single slot requests and error if misused (for example, PFCOUNT).

12.3. Working with RedisTemplate and ClusterOperations

See the Working with Objects through RedisTemplate section for information about the general purpose, configuration, and usage of RedisTemplate.

Be careful when setting up RedisTemplate#keySerializer using any of the JSON RedisSerializers, as changing JSON structure has immediate influence on hash slot calculation.

RedisTemplate provides access to cluster-specific operations through the ClusterOperations interface, which can be obtained from RedisTemplate.opsForCluster(). This lets you explicitly run commands on a single node within the cluster while retaining the serialization and deserialization features configured for the template. It also provides administrative commands (such as CLUSTER MEET) or more high-level operations (for example, resharding).

The following example shows how to access RedisClusterConnection with RedisTemplate:

Example 8. Accessing RedisClusterConnection with RedisTemplate
ClusterOperations clusterOps = redisTemplate.opsForCluster();
clusterOps.shutdown(NODE_7379);                                              (1)
1 Shut down node at 7379 and cross fingers there is a replica in place that can take over.

13. Redis Repositories

Working with Redis Repositories lets you seamlessly convert and store domain objects in Redis Hashes, apply custom mapping strategies, and use secondary indexes.

Redis Repositories require at least Redis Server version 2.8.0 and do not work with transactions. Make sure to use a RedisTemplate with disabled transaction support.

13.1. Usage

Spring Data Redis lets you easily implement domain entities, as shown in the following example:

Example 9. Sample Person Entity
@RedisHash("people")
public class Person {

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

We have a pretty simple domain object here. Note that it has a @RedisHash annotation on its type and a property named id that is annotated with org.springframework.data.annotation.Id. Those two items are responsible for creating the actual key used to persist the hash.

Properties annotated with @Id as well as those named id are considered as the identifier properties. Those with the annotation are favored over others.

To now actually have a component responsible for storage and retrieval, we need to define a repository interface, as shown in the following example:

Example 10. Basic Repository Interface To Persist Person Entities
public interface PersonRepository extends CrudRepository<Person, String> {

}

As our repository extends CrudRepository, it provides basic CRUD and finder operations. The thing we need in between to glue things together is the corresponding Spring configuration, shown in the following example:

Example 11. JavaConfig for Redis Repositories
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

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

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

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

Given the preceding setup, we can inject PersonRepository into our components, as shown in the following example:

Example 12. Access to Person Entities
@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 Generates a new id if the current value is null or reuses an already set id value and stores properties of type Person inside the Redis Hash with a key that has a pattern of keyspace:id — in this case, it might be people:5d67b7e1-8640-4475-beeb-c666fab4c0e5.
2 Uses the provided id to retrieve the object stored at keyspace:id.
3 Counts the total number of entities available within the keyspace, people, defined by @RedisHash on Person.
4 Removes the key for the given object from Redis.

13.2. Object Mapping Fundamentals

This section covers the fundamentals of Spring Data object mapping, object creation, field and property access, mutability and immutability. Note, that this section only applies to Spring Data modules that do not use the object mapping of the underlying data store (like JPA). Also be sure to consult the store-specific sections for store-specific object mapping, like indexes, customizing column or field names or the like.

Core responsibility of the Spring Data object mapping is to create instances of domain objects and map the store-native data structures onto those. This means we need two fundamental steps:

  1. Instance creation by using one of the constructors exposed.

  2. Instance population to materialize all exposed properties.

13.2.1. Object creation

Spring Data automatically tries to detect a persistent entity’s constructor to be used to materialize objects of that type. The resolution algorithm works as follows:

  1. If there is a single static factory method annotated with @PersistenceCreator then it is used.

  2. If there is a single constructor, it is used.

  3. If there are multiple constructors and exactly one is annotated with @PersistenceCreator, it is used.

  4. If there’s a no-argument constructor, it is used. Other constructors will be ignored.

The value resolution assumes constructor/factory method argument names to match the property names of the entity, i.e. the resolution will be performed as if the property was to be populated, including all customizations in mapping (different datastore column or field name etc.). This also requires either parameter names information available in the class file or an @ConstructorProperties annotation being present on the constructor.

The value resolution can be customized by using Spring Framework’s @Value value annotation using a store-specific SpEL expression. Please consult the section on store specific mappings for further details.

Object creation internals

To avoid the overhead of reflection, Spring Data object creation uses a factory class generated at runtime by default, which will call the domain classes constructor directly. I.e. for this example type:

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

we will create a factory class semantically equivalent to this one at runtime:

class PersonObjectInstantiator implements ObjectInstantiator {

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

This gives us a roundabout 10% performance boost over reflection. For the domain class to be eligible for such optimization, it needs to adhere to a set of constraints:

  • it must not be a private class

  • it must not be a non-static inner class

  • it must not be a CGLib proxy class

  • the constructor to be used by Spring Data must not be private

If any of these criteria match, Spring Data will fall back to entity instantiation via reflection.

13.2.2. Property population

Once an instance of the entity has been created, Spring Data populates all remaining persistent properties of that class. Unless already populated by the entity’s constructor (i.e. consumed through its constructor argument list), the identifier property will be populated first to allow the resolution of cyclic object references. After that, all non-transient properties that have not already been populated by the constructor are set on the entity instance. For that we use the following algorithm:

  1. If the property is immutable but exposes a with… method (see below), we use the with… method to create a new entity instance with the new property value.

  2. If property access (i.e. access through getters and setters) is defined, we’re invoking the setter method.

  3. If the property is mutable we set the field directly.

  4. If the property is immutable we’re using the constructor to be used by persistence operations (see Object creation) to create a copy of the instance.

  5. By default, we set the field value directly.

Property population internals

Similarly to our optimizations in object construction we also use Spring Data runtime generated accessor classes to interact with the entity instance.

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;
  }
}
Example 13. A generated Property Accessor
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)
    }
  }
}
1 PropertyAccessor’s hold a mutable instance of the underlying object. This is, to enable mutations of otherwise immutable properties.
2 By default, Spring Data uses field-access to read and write property values. As per visibility rules of private fields, MethodHandles are used to interact with fields.
3 The class exposes a withId(…) method that’s used to set the identifier, e.g. when an instance is inserted into the datastore and an identifier has been generated. Calling withId(…) creates a new Person object. All subsequent mutations will take place in the new instance leaving the previous untouched.
4 Using property-access allows direct method invocations without using MethodHandles.

This gives us a roundabout 25% performance boost over reflection. For the domain class to be eligible for such optimization, it needs to adhere to a set of constraints:

  • Types must not reside in the default or under the java package.

  • Types and their constructors must be 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 クラスはインスタンス化がサポートされており、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の 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 複合型のプロパティは、ドットパスによってマップされます。

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

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

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

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({"eye-color"、"grey" }、{" …

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

リスト
複合型

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

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

地図
複合型

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

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

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

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

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

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

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

  private final Jackson2JsonRedisSerializer<Address> serializer;

  public AddressToBytesConverter() {

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

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

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

  private final Jackson2JsonRedisSerializer<Address> serializer;

  public BytesToAddressConverter() {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

    mappingRedisConverter.setCustomConversions(customConversions);

    return mappingRedisConverter;
  }

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

13.4. キースペース

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

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

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

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

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

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

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

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

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

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

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

13.5. 二次インデックス

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

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

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

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

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

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

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

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

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

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

@RedisHash("people")
public class Person {

  // ... other properties omitted

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

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

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

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  public static class MyIndexConfiguration extends IndexConfiguration {

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

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

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

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

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

  public static class MyIndexConfiguration extends IndexConfiguration {

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

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

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

@RedisHash("people")
public class Person {

  Address address;

  // ... other properties omitted
}

public class Address {

  @GeoIndexed Point location;

  // ... other properties omitted
}

public interface PersonRepository extends CrudRepository<Person, String> {

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

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

repository.save(rand);                                                        (3)

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

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

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

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

13.6.1. 導入

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

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

13.6.2. 使用方法

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // … getters and setters omitted
}

前の例は、単純なドメインオブジェクトを示しています。これを使用して Example を作成できます。デフォルトでは、null 値を持つフィールドは無視され、文字列はストア固有のデフォルトを使用して照合されます。

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

例は、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 設定の範囲を説明しています。

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

null ハンドリング

ExampleMatcher

文字列マッチング

ExampleMatcher and property path

Ignoring properties

Property path

Case sensitivity

ExampleMatcher and property path

Value transformation

Property path

13.6.4. Running an Example

The following example uses Query by Example against a repository:

Example 30. 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 31. 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 32. 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 33. 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 34. 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 35. 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 12. 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 を構築します。次の例を見てみましょう。

例 36: クエリ結果の並べ替え
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() {

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

    return jedisConnectionFactory;
  }

  void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {

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

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

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

    return template;
  }

}

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

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

class RepositoryClient {

  @Inject
  PersonRepository repository;

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

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

13.13. Redis リポジトリの構造

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

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

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

例 37: エンティティの例
@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: コマンドリファレンス

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

表 13: 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

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

ZRANK

ZREM

ZREMRANGEBYLEX

-

ZREMRANGEBYRANK

ZREVRANGE

ZREVRANGEBYSCORE

ZREVRANK

ZSCAN

ZSCORE

ZUNINONSTORE