Spring Redis アプリケーションを開発する

データストアに Redis を使用して Spring ベースのアプリケーションを構築する

Redis は、キー・バリュー型の NoSQL データストア・ソリューションです。Spring Data Redis は、Java 開発者が下位レベルの Redis API を直接操作することなく、Redis を使用して Spring アプリケーションを構築できるようにするための、Spring ポートフォリオ・プロジェクトです。この記事では Redis について紹介した後、Spring Data Redis API を使用して単純な辞書アプリケーションを構築する方法を説明します。

Shekhar Gulati, Java developer, Freelance

Photo of Shekar GulatiShekhar Gulati は、コードの作成や新しいテクノロジーの学習に時間を使うのが好きな Java 開発者です。彼は、さまざまなカンファレンスに参加してアクティブに講演を行ったり、developerWorks、Java Lobby、Developer.com などに掲載される技術記事を書いたりしています。彼について知るには、ブログを読んだり、Twitter で @shekhargulati をフォローしたりすることができます。



2013年 9月 19日

オープンソースの Spring フレームワークはエンタープライズ・アプリケーションを開発する上での大きな柱であり、何百万人もの Java 開発者が Spring フレームワークを使用しています。この Spring を使用して、データ・アクセス技術 ― 非リレーショナル・データベース、MapReduce フレームワーク、クラウド・ベースのデータ・サービスといった最近の技術を含みます ― を用いるアプリケーションを簡単に構築できるようにするためのオープンソースのアンブレラ・プロジェクトが Spring Data です。このようなデータ・アクセス技術の 1 つに、ANSI C で作成されたオープンソースの高度なキー・バリュー型 NoSQL データストアである Redis (REmote DIctionary Server) があります。この記事では、Redis の概要と、Redis のデータ・モデルおよびデータ型、さらには Redis が優れている点を紹介します。その後、Spring Data Redis を使用してサンプル・アプリケーションを構築する方法を説明します。

Redis の紹介

Redis はインメモリー・データストアであり、データが失われることがないようにディスクにデータを書き込むこともできます。Redis でデータを永続化する方法には、RDB と AOF という 2 つの方法があります。RDB による永続化は、指定された間隔で、その時点でのデータ・セットのスナップショットをとります。この方法はあまり確実なものではなく、一部のデータを失う可能性がありますが、非常に高速です。AOF による永続化は、RDB よりもはるかに確実であり、サーバーが受信する書き込み操作のすべてをログに記録します。書き込み操作はサーバーの起動時に再度実行され、元のデータ・セットが再構築されます。Redis に対してクエリーを実行する場合、データはメモリーから提供され、ディスクから提供されることは決してありません。また、Redis はメモリー内に格納されているキーと値に対してすべての操作を実行します。

Redis はクライアント/サーバー・モデルに従っており、TCP ポートをリッスンしてコマンドを受け付けます。また、Redis のコマンドはすべてアトミックであるため、異なるクライアントから同じキーを使用してもレース・コンディションが生じることはありません。インメモリー・オブジェクト・キャッシング・システムである memcached を扱ってみると、Redis と似ていると思うはずですが、言ってみれば Redis は memcached++ です (Redis と memcached との比較については、Andrew Glover 氏による記事「Java 開発 2.0: Redis を実際に使用する」を参照してください)。Redis はデータの複製もサポートしています。

データ・モデル

Redis のデータ・モデルは、RDBMS (リレーショナル・データベース管理システム) とも、通常の NoSQL キー・バリュー型データストアとも異なります。Redis のデータ型は、プログラミング言語の基本データ型と似ているため、開発者にとっては自然に感じられます。すべてのデータ型は、そのデータ型に対して適用できる操作をサポートしています。サポートされているデータ型は以下のとおりです。

  • 文字列型
  • リスト型
  • セット型
  • ソート済みセット型
  • ハッシュ型

主な利点

Redis の利点は、その速さ、豊富なデータ型のサポート、操作のアトミック性、そして汎用性にあります。

  • Redis は非常に高速であり、SET 操作を毎秒約 10 万回、GET 操作を毎秒約 10 万回実行することができます。redis-benchmark ユーティリティーを使用すると、皆さんのマシンで Redis のパフォーマンスを測定することができます (redis-benchmark は、N 個のクライアントが同時に SET/GET を実行するシミュレーションを行い、クエリーを合計 M 回実行します)。
  • Redis では、大部分の開発者が既に知っているデータ型の大半をネイティブ・サポートしているため、さまざまな問題を簡単に解決することができます。どの問題にどのデータ型で対処するのが最善であるかは、皆さんの経験を基に判断することができます。
  • Redis の操作はすべてアトミックなため、複数のクライアントが同時に Redis サーバーにアクセスしても、すべてのクライアントが同じ更新されたデータを取得することができます。
  • Redis はさまざまな用途 ― キャッシング用、メッセージング・キュー用 (Redis はパブリッシュ/サブスクライブをネイティブ・サポートしています)、そして Web セッションや Web ページのヒット数など存続期間の短いアプリケーション・データ用 ― で役立つ多用途のツールです。

Redis を使用する

Linux や UNIX で Redis を使用するには、以下のように圧縮された .tar ファイルをダウンロードし (「参考文献」を参照)、解凍したら、make コマンドを実行します。

wget http://redis.googlecode.com/files/redis-2.6.7.tar.gz
tar xzf redis-2.6.7.tar.gz
cd redis-2.6.7
make

これにより、コンパイルされたバイナリー・ファイルが src ディレクトリーに得られます。以下のように入力して Redis を実行します。

src/redis-server

すると、Redis は以下のような出力で応答します。

[988] 05 Jan 14:41:00.230 # Warning: no config file specified, using the default config. 
In order to specify a config file use src/redis-server /path/to/redis.conf 

[988] 05 Jan 14:41:00.231 * Max number of open files set to 10032
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 2.6.7 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in stand alone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 988
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               
[988] 05 Jan 14:41:00.238 # Server started, Redis version 2.6.7
[988] 05 Jan 14:41:00.239 * DB loaded from disk: 0.000 seconds
[988] 05 Jan 14:41:00.239 * The server is now ready to accept connections on port 6379

組み込みのクライアントを使用して Redis を操作するには、以下のようにコマンドラインからクライアントを起動します。

src/redis-cli

以下のクライアント・セッションは Redis が ping コマンドと INFO コマンドに応答する様子を示しています。

redis 127.0.0.1:6379> ping
PONG
redis 127.0.0.1:6379> 
redis 127.0.0.1:6379> 
redis 127.0.0.1:6379> INFO
# Server
redis_version:2.6.7
redis_git_sha1:00000000
redis_git_dirty:0
redis_mode:standalone
os:Darwin 12.0.0 x86_64
arch_bits:64
multiplexing_api:kqueue
gcc_version:4.2.1
process_id:3449
run_id:270454ebad19fbc851194548569efca6ac63e00a
tcp_port:6379
uptime_in_seconds:95
uptime_in_days:0
lru_clock:1407736
....

Redis のデータ型と例

ここでは Redis がサポートするデータ型について簡単に説明し、組み込みクライアントを使用する簡単な例を紹介します。

文字列型

文字列は Redis がサポートする基本データ型です。SET コマンドでキーの文字列の値を設定することができ、また GET コマンドでキーの文字列の値を取得することができます。

redis> SET firstname shekhar
OK
redis> SET lastname gulati
OK
redis> GET firstname
"shekhar"
redis> GET lastname
"gulati"

キーの値が整数の場合、DECR または DECRBY を使用して値をデクリメントすることや、INCR または INCRBY を使用して値をインクリメントすることができます。これらの操作は何かのカウント値 (例えば Web ページのヒット数など) を保持したい場合に便利です。

redis> INCR votes
(integer) 1
redis> INCR votes
(integer) 2
redis> INCR votes
(integer) 3
redis> DECR votes
(integer) 2

その他にも、APPENDGETRANGEMSETSTRLENGTH など、文字列に使用できる操作はいくつもあります。「参考文献」で Redis のすべてのデータ型に対する操作の完全な一覧へのリンクを参照してください。

リスト型

Redis のリストは文字列で構成される順序付きの集合であり、要素 (一意である必要はありません) をいくつでも追加することができます。Redis では、リストへ要素を追加する操作や、リストから要素を取得する操作の他に、pop、push、range、その他いくつかのリストに関する操作をサポートしています。

ゼロ・ベースのリスト

Redis のリストはゼロ・ベースです。

例えば、単語 (一意であるかどうかは問いません) のリストを保持し、最も新しくシステムに追加された 3 つの単語を取得したいとします。

リストに単語を追加するには、LPUSH コマンドを使用することができます。このコマンドにより、1 つ以上の値がリストの先頭に追加されます。

redis> LPUSH words austerity
(integer) 1
redis> LPUSH words socialism moratorium socialism socialism
(integer) 5

LPUSH を使用すると、最も新しく追加された単語がリストの先頭になり、それまで先頭にあった単語は先頭から下に下がります。LRANGE コマンドを使用すると、例えば以下のようにリストの先頭にある 3 つの単語を表示することができます。

redis> LRANGE words 0 2
1) "socialism"
2) "socialism"
3) "moratorium"

リストの長さを取得するには、以下のように LLEN コマンドを使用します。

redis > LLEN words
(integer) 5

リストから要素を削除するには、LREM コマンドを使用します。このコマンドを、例えば以下のように使用すると、リストに含まれるすべての socialism を削除することができます。

redis> LREM words 0 socialism
(integer) 2

リストを削除するには、以下のように words キーを削除する必要があります。

redis 127.0.0.1:6379> DEL words
(integer) 1

セット型

セットは一意の要素で構成される順序なしの集合です。セットに要素を追加するには SADD コマンドを使用し、セットの全メンバーを取得するには SMEMBERS コマンドを使用します。一例として、システムに追加されたすべての重複しない単語で構成される集合 (uniquewords という名前にします) を保持したいとします。以下を見るとわかるように、セットには同じ単語 (socialism) を 2 度追加することはできません。

redis> SADD uniquewords austerity
(integer) 1
redis> SADD uniquewords pragmatic
(integer) 1
redis> SADD uniquewords moratorium
(integer) 1
redis> SADD uniquewords socialism
(integer) 1
redis> SADD uniquewords socialism
(integer) 0

uniquewords セットに含まれるすべての単語を表示するには、以下のように SMEMBERS コマンドを使用します。

redis 127.0.0.1:6379> SMEMBERS uniquewords
1) "moratorium"
2) "austerity"
3) "socialism"
4) "pragmatic"

また、複数のセットに対して積集合コマンドや和集合コマンドを実行することもできます。以下の例では newwords という名前の別のセットを作成し、そこにいくつかの要素を追加しています。

redis 127.0.0.1:6379> SADD newwords austerity good describe strange
(integer) 4

uniquewordsnewwords という 2 つのセットに共通するすべての要素を確認するには、以下のように SINTER コマンドを使用します。

redis 127.0.0.1:6379> SINTER uniquewords newwords
1) "austerity"

複数のセットを結合するには SUNION コマンドを使用します。以下の例を見るとわかるように、austerity という単語は一度しか追加されていません。

redis 127.0.0.1:6379> SUNION uniquewords newwords
1) "austerity"
2) "strange"
3) "describe"
4) "socialism"
5) "pragmatic"
6) "good"
7) "moratorium"

ソート済みセット型

ソート済みセットは、あるスコアに基づいてソートすることが可能なセットで、一意の要素のみが含まれます。このセットに値を追加する際には必ず、ソートで使用されるスコアを指定する必要があります。

例えば、一連の単語を単語の長さに従ってソートしたいとします。ソート済みセットに要素を追加するには ZADD コマンドを使用し、ZADD <キー> <スコア> <バリュー> という構文を使用します。ZRANGE コマンドを使用すると、ソート済みセット内の要素をスコア順に表示することができます。

redis> ZADD wordswithlength 9 austerity
(integer) 1
redis> ZADD wordswithlength 7 furtive
(integer) 1
redis> ZADD wordswithlength 5 bigot
(integer) 1
redis> ZRANGE wordswithlength 0 -1
1) "bigot"
2) "furtive"
3) "austerity"

ソート済みセットのサイズを取得するには、以下のように ZCARD コマンドを使用します。

redis 127.0.0.1:6379> ZCARD wordswithlength
(integer) 3

ハッシュ型

ハッシュを使用すると、ハッシュに対するキー・バリュー・ペアを格納することができます。この方法は、いくつかのプロパティーを持つオブジェクトを格納したい場合に役立つ可能性があります。以下はその一例です。

redis> HSET user:1 name shekhar
(integer) 1
redis> HSET user:1 lastname gulati
(integer) 1
redis> HGET user:1
redis> HGET user:1 name
"shekhar"
redis> HGETALL user:1
1) "name"
2) "shekhar"
3) "lastname"
4) "gulati"

これで Redis のデータ型について理解できたので、Redis をバックエンドのデータストアに使用するアプリケーションを、Spring Data Redis フレームワークを使用して構築する準備が整いました。


Spring Redis アプリケーションを開発する

前提条件

この記事のサンプル・アプリケーションを構築するには、以下のものが必要です。

  • Redis
  • JDK 6 以上
  • Apache Maven 3 以上
  • Spring Tool Suite
  • Git

Java 開発者は Spring Data Redis を使用して、プログラムで Redis にアクセスし、さまざまな操作を実行することができます。Spring フレームワークでは、生産性、一貫性、移植性を非常に重視した POJO (Plain Old Java Object) ベースのプログラミング・モデルを常に推進してきました。こうした価値観は Spring Data Redis プロジェクトにも受け継がれています。

Spring Data Redis では、既存の Redis クライアント・ライブラリー (Jedis、JRedis、Redis プロトコル、RJC など) に対する抽象化が行われており (「参考文献」を参照)、Redis を操作するためのボイラープレート・コードをなくすことで、下位レベルの Redis API について学習しなくても、簡単にキー・バリュー型データストアである Redis を扱えるようにしています。また Spring Data Redis には、Redis を操作するための RedisTemplate という (JDBCTemplateHibernateTemplate と似ている) 汎用的なテンプレート・クラスも用意されています。RedisTemplate は、オブジェクト指向による方法で Redis を操作するためのメインのクラスであり、オブジェクトのシリアライズと型変換を行ってくれるため、開発者はオブジェクトを扱えばよく、シリアライズや型変換について気にする必要がありません。

ここで構築するアプリケーションは単純な辞書アプリケーションです (このアプリケーションの完全なコードは GitHub からダウンロードすることができます。「参考文献」を参照)。辞書はさまざまな単語の集合であり、各単語はいくつもの意味を持っています。この辞書アプリケーションは、Redis のリスト・データ型に容易にモデル化することができ、各単語をリストのキーとし、単語の意味をそのキーのバリューとすることができます (単語の意味を一意にしたい場合には、リスト型の代わりにセット型を使用することもできますし、意味をソートしたい場合には、ソート済みセット型を使用することもできます)。例えば、astonishing という単語をキーに、そのキーのバリューを astoundingstaggering にすることができます。

まず、単語とその意味で構成される簡単なリストを作成することから始めます。redis-server コマンドで Redis を起動し、redis-cli コマンドでクライアントを起動します。続いて、以下のクライアント・セッションに示すコマンドを実行します。

redis> RPUSH astonishing astounding
(integer) 1
redis> RPUSH astonishing staggering
(integer) 2
redis> LRANGE astonishing 0 -1
1) "astounding"
2) "staggering"

これで astonishing というリストが作成され、astoundingstaggering という意味が astonishing リストの最後に格納されました。LRANGE コマンドを使用すると、astonishing のすべての意味を取得することができます。

Spring Tool Suite を使用してテンプレート・プロジェクトを作成する

今度は、Spring テンプレート・プロジェクトを作成して、アプリケーションで使用できるようにする必要があります。Spring Tool Suite を開いて、「File (ファイル)」 -> 「New (新規)」 -> 「Spring Template Project (Spring テンプレート・プロジェクト)」 -> 「Simple Spring Utility Project (単純な Spring ユーティリティー・プロジェクト)」の順に選択し、入力を促されたら「Yes (はい)」をクリックします。プロジェクト名として「dictionary」と入力し、最上位レベルのパッケージ名を入力します (私の場合は「com.shekhar.dictionary」でした)。これで、Spring Tool Suite のワークスペースに dictionary というサンプル・プロジェクトが作成されました。

依存関係で pom.xml を更新する

この dictionary プロジェクトには、Spring Data Redis プロジェクトに関連付けられた依存関係がありません。依存関係を追加するには、上記で作成したテンプレート・プロジェクトの pom.xml をリスト 1 の pom.xml で置き換えます (このファイルでは、この記事の執筆時点で最新のリリースである Spring Data Redis プロジェクトの 1.0.2 リリースを使用しています)。

リスト 1. Spring Data Redis に依存関係を追加するための置き換え用の pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
   http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.shekhar</groupId>
   <artifactId>dictionary</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>dictionary</name>
   <url>http://maven.apache.org</url>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   </properties>

   <repositories>
      <repository>
         <id>spring-release</id>
         <name>Spring Maven RELEASE Repository</name>
         <url>http://maven.springframework.org/release</url>
      </repository>
   </repositories>
   <dependencies>
      <dependency>
         <groupId>javax.inject</groupId>
         <artifactId>javax.inject</artifactId>
         <version>1</version>
      </dependency>
      <dependency>
         <groupId>cglib</groupId>
         <artifactId>cglib</artifactId>
         <version>2.2.2</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.data</groupId>
         <artifactId>spring-data-redis</artifactId>
         <version>1.0.2.RELEASE</version>
      </dependency>
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-test</artifactId>
         <version>3.1.2.RELEASE</version>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.8.1</version>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
               <source>1.6</source>
               <target>1.6</target>
            </configuration>
         </plugin>
      </plugins>
   </build>
</project>

RedisConnectionFactoryRedisTemplate を構成する

RedisConnectionFactory は、Redis への接続を作成するためのスレッド・セーフな接続ファクトリーであり、RedisConnection は、Redis に対する存続時間の短い、スレッド・セーフではない接続です。RedisConnection は Redis コマンドとの 1 対 1 のマッピングを提供する一方、RedisConnectionFactory はボイラープレート・コードをなくす上で有効なコンビニエンス・メソッドを提供します。RedisConnectionFactory を使用すると、Bean を定義するのと同じくらい簡単に、さまざまな Redis クライアント API の間での切り替えを行うことができます。このサンプル・アプリケーションには JedisConnectionFactory を使用しますが、他の任意の ConnectionFactory のバリエーションを使用することもできます。

リスト 2 のように LocalRedisConfig クラスを作成します。

リスト 2. LocalRedisConfig クラスを作成する
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;

import redis.clients.jedis.JedisPoolConfig;

@Configuration
@ComponentScan(basePackages="com.shekhar.dictionary.dao")
public class LocalRedisConfig {

@Bean
public RedisConnectionFactory jedisConnectionFactory(){
   JedisPoolConfig poolConfig = new JedisPoolConfig();
   poolConfig.maxActive = 10;
   poolConfig.maxIdle = 5;
   poolConfig.minIdle = 1;
   poolConfig.testOnBorrow = true;
   poolConfig.testOnReturn = true;
   poolConfig.testWhileIdle = true;
   JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(poolConfig);
   return jedisConnectionFactory;
}

@Bean
public StringRedisTemplate redisTemplate(){
   StringRedisTemplate redisTemplate = new StringRedisTemplate(jedisConnectionFactory());
   return redisTemplate;
}
}

LocalRedisConfig では、JedisConnectionFactory という Bean と SpringRedisTemplate という Bean を定義します。SpringRedisTemplate は、文字列を扱うことに特化した RedisTemplate のバージョンです。

JUnit テストを使用することで、構成をテストすることができます (リスト 3)。

リスト 3. 構成をテストする
import javax.inject.Inject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration(classes = LocalRedisConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class LocalRedisConfigTest {

   @Inject
   private JedisConnectionFactory jedisConnectionFactory;
   
   @Inject
   private StringRedisTemplate redisTemplate;
   
   @Test
   public void testJedisConnectionFactory() {
      assertNotNull(jedisConnectionFactory);
   }

   @Test
   public void testRedisTemplate() {
      assertNotNull(redisTemplate);
   }

}

DictionaryDao を作成する

今度は DictionaryDao クラスを作成します (リスト 4)。

リスト 4. DictionaryDao クラスを作成する
import org.springframework.data.redis.core.StringRedisTemplate;

@Repository
public class DictionaryDao {

   private static final String ALL_UNIQUE_WORDS = "all-unique-words";
   
   private StringRedisTemplate redisTemplate;

   @Inject
   public DictionaryDao(StringRedisTemplate redisTemplate){
      this.redisTemplate = redisTemplate;
   }
   
   public Long addWordWithItsMeaningToDictionary(String word, String meaning) {
      Long index = redisTemplate.opsForList().rightPush(word, meaning);
      return index;
   }
}

DictionaryDao を使用すると、Redis でさまざまな操作を行うことができます。リスト 4 を見るとわかるように、Spring Data Redis プロジェクトのコア・クラスである RedisTemplateDictionaryDao に注入します。リスト 4StringRedisTemplate は、文字列データ型を扱うための RedisTemplate の子クラスです。

RedisTemplate は、ValueOperationsListOperationsSetOperationsHashOperationsZSetOperations などのキー型の操作を提供します。リスト 4 では、ListOperations を使用して新しい単語を Redis データストアに格納しています。rightPush() 操作は、単語とその意味をリストの最後に追加します。dictionaryDao.addWordWithItsMeaningToDictionary() メソッドはリストに追加された要素のインデックスを返します。

リスト 5 に、dictionaryDao.addWordWithItsMeaningToDictionary() に対する JUnit テスト・ケースを示します。

リスト 5. dictionaryDao.addWordWithItsMeaningToDictionary() メソッドをテストする
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.hasItems;

import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.shekhar.dictionary.config.LocalRedisConfig;

@ContextConfiguration(classes = {LocalRedisConfig.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class DictionaryDaoTest {

   @Inject
   private DictionaryDao dictionaryDao;

   @Inject
   private StringRedisTemplate redisTemplate;

   @Test
   public void testAddWordWithItsMeaningToDictionary() {
      String meaning = "To move forward with a bounding, drooping motion.";
      Long index = dictionaryDao.addWordWithItsMeaningToDictionary("lollop",
            meaning);
      assertThat(index, is(notNullValue()));
      assertThat(index, is(equalTo(1L)));
   }

このテストを初めて実行すると、テストに合格し、単語が Redis に格納されます。しかしこのテストを再度実行すると、Redis は同じ意味を再度追加することになるため、テストの結果は失敗となり、その実行結果を示すインデックスとして 2 が返されます。従ってテストを実行するたびに、サーバー・コマンド flushAll() または flushDb() のいずれかを使用して Redis データストアをクリアする必要があります。flushAll() コマンドはすべてのデータベースからすべてのキーを削除しますが、flushDb() は現在のデータベースからのみキーを削除します。リスト 6 は修正されたテストを示しています。

リスト 6. dictionaryDao.addWordWithItsMeaningToDictionary() に対する修正されたテスト
@ContextConfiguration(classes = {LocalRedisConfig.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class DictionaryDaoTest {

   @Inject
   private DictionaryDao dictionaryDao;

   @Inject
   private StringRedisTemplate redisTemplate;

   @After
   public void tearDown() {
      redisTemplate.getConnectionFactory().getConnection().flushDb();
   }

   @Test
   public void testAddWordWithItsMeaningToDictionary() {
      String meaning = "To move forward with a bounding, drooping motion.";
      Long index = dictionaryDao.addWordWithItsMeaningToDictionary("lollop",
            meaning);
      assertThat(index, is(notNullValue()));
      assertThat(index, is(equalTo(1L)));
   }

   @Test
   public void shouldAddMeaningToAWordIfItExists() {
      Long index = dictionaryDao.addWordWithItsMeaningToDictionary("lollop",
            "To move forward with a bounding, drooping motion.");
      assertThat(index, is(notNullValue()));
      assertThat(index, is(equalTo(1L)));
      index = dictionaryDao.addWordWithItsMeaningToDictionary("lollop",
            "To hang loosely; droop; dangle.");
      assertThat(index, is(equalTo(2L)));
   }
}

これで、1 つの単語をデータストアに格納する機能が用意できたので、次のステップでは 1 つの単語の意味をすべて取得する機能を作成します。このステップの処理は、Listrange 操作を使用すると簡単に行うことができます。range() メソッドは、キーの名前、範囲の開始点、範囲の終了点、という 3 つの引数を取ります。1 つの単語のすべての意味を取得するには、以下のように開始点を 0 にし、終了点を -1 にします。

public List<String> getAllTheMeaningsForAWord(String word) {
      List<String> meanings = redisTemplate.opsForList().range(word, 0, -1);
      return meanings;
}

このアプリケーションに必要なもう 1 つの基本機能は、単語を削除する機能です。そのためには RedisTemplate クラスの delete を使用することができます。delete 操作はキーの集合を引数に取ります。

public void removeWord(String word) {
      redisTemplate.delete(Arrays.asList(word));
}

public void removeWords(String... words) {
      redisTemplate.delete(Arrays.asList(words));
}

新たに追加された read 操作と delete 操作のための JUnit テスト・ケースをリスト 7 に示します。

リスト 7. read 操作と delete 操作のための JUnit テスト
@Test
   public void shouldGetAllTheMeaningForAWord() {
      setupOneWord();
      List<String> allMeanings = dictionaryDao
            .getAllTheMeaningsForAWord("lollop");
      assertThat(allMeanings.size(), is(equalTo(2)));
      assertThat(
            allMeanings,
            hasItems("To move forward with a bounding, drooping motion.",
                  "To hang loosely; droop; dangle."));
   }

   @Test
   public void shouldDeleteAWordFromDictionary() throws Exception {
      setupOneWord();
      dictionaryDao.removeWord("lollop");
      List<String> allMeanings = dictionaryDao
            .getAllTheMeaningsForAWord("lollop");
      assertThat(allMeanings.size(), is(equalTo(0)));
   }

   @Test
   public void shouldDeleteMultipleWordsFromDictionary() {
      setupTwoWords();
      dictionaryDao.removeWords("fain", "lollop");
      List<String> allMeaningsForLollop = dictionaryDao
            .getAllTheMeaningsForAWord("lollop");
      List<String> allMeaningsForFain = dictionaryDao
            .getAllTheMeaningsForAWord("fain");
      assertThat(allMeaningsForLollop.size(), is(equalTo(0)));
      assertThat(allMeaningsForFain.size(), is(equalTo(0)));
   }
}

まとめ

この記事では、Redis について紹介するとともに、Spring Data Redis プロジェクトを使用して Spring Redis アプリケーションを構築する方法を説明しました。その中で、Redis の基本、Redis のデータ・モデル、Redis のデータ型、そして Spring Redis を使い始める方法を説明しました。Redis には、他にも Redis PubSub や MultI-EXEC をはじめとし、詳しく探ってみる価値のある機能が豊富にあります。これらのトピックに関する情報は、この記事の「参考文献」を調べてください。

参考文献

学ぶために

製品や技術を入手するために

  • Redis: .tar に圧縮された Redis ファイルをダウンロードしてください。
  • Spring Tool Suite: STS をダウンロードしてください。

議論するために

  • developerWorks コミュニティーに参加して、開発者によるブログ、フォーラム、グループ、Wiki を調べ、仲間やエキスパートとのつながりを持ってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=945023
ArticleTitle=Spring Redis アプリケーションを開発する
publish-date=09192013