Apache Geodeでの、検索機能を試してみるということで。
とりあえずは、こちらのページをもとにGetting Started的な。
http://geode.docs.pivotal.io/docs/getting_started/querying_quick_reference.html
どうも、SQLに似たOQL(Object Query Language)というものを使うようです。
http://geode.docs.pivotal.io/docs/developing/querying_basics/query_basics.html
http://geode.docs.pivotal.io/docs/developing/querying_basics/oql_compared_to_sql.html
とりあえず、サンプルを見つつ試してみましょう。
使用するApache Geodeのバージョンは、1.0.0-incubating.M1とします。
準備
Maven依存関係は、以下のように定義。
<dependency> <groupId>org.apache.geode</groupId> <artifactId>gemfire-core</artifactId> <version>1.0.0-incubating.M1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.3.0</version> <scope>test</scope> </dependency>
JUnitとAssertJは、テスト用です。
Geodeの設定ファイルは、特に用意しません。
テストで使うコード
今回は、Geodeに格納するためのクラスを作成します。
テーマは、書籍で。書籍にしてはフィールド数が少ないですが、まあ簡単にということで。デフォルトコンストラクタに加えて、引数ありのものを付けているのも、簡単のためです…。
src/test/java/org/littlewings/geode/query/Book.java
package org.littlewings.geode.query; import java.io.Serializable; public class Book implements Serializable { private static final long serialVersionUID = 1L; private String isbn; private String title; private int price; public Book() { } public Book(String isbn, String title, int price) { this.isbn = isbn; this.title = title; this.price = price; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } }
用意するクラスは、少なくともSerializableである必要があります。
テストコードは、以下のクラスの中で書きます。
src/test/java/org/littlewings/geode/query/SimpleQueryTest.java
package org.littlewings.geode.query; import java.util.Arrays; import java.util.List; import com.gemstone.gemfire.cache.Cache; import com.gemstone.gemfire.cache.CacheFactory; import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.cache.RegionShortcut; import com.gemstone.gemfire.cache.query.FunctionDomainException; import com.gemstone.gemfire.cache.query.NameResolutionException; import com.gemstone.gemfire.cache.query.Query; import com.gemstone.gemfire.cache.query.QueryInvocationTargetException; import com.gemstone.gemfire.cache.query.QueryService; import com.gemstone.gemfire.cache.query.SelectResults; import com.gemstone.gemfire.cache.query.Struct; import com.gemstone.gemfire.cache.query.TypeMismatchException; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class SimpleQueryTest { // ここに、テストを書く! protected List<Book> getBooks() { return Arrays.asList( new Book("978-4777518654", "はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発", 2700), new Book("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104), new Book("978-4048662024", "高速スケーラブル検索エンジン ElasticSearch Server", 3024) ); } }
データ登録用のメソッドを、とりあえず。
はじめてのOQL
では、Geodeでクエリを投げてみます。
書いてみたのが、こちら。
@Test public void queryGettingStarted() throws NameResolutionException, TypeMismatchException, QueryInvocationTargetException, FunctionDomainException { try (Cache cache = new CacheFactory().create()) { Region<String, Book> region = cache.<String, Book>createRegionFactory(RegionShortcut.PARTITION).create("bookRegion"); getBooks().forEach(b -> region.put(b.getIsbn(), b)); QueryService queryService = cache.getQueryService(); Query query = queryService.newQuery("SELECT * FROM /bookRegion ORDER BY price ASC"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(3); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発"); assertThat(resultBooks.get(1).getTitle()) .isEqualTo("高速スケーラブル検索エンジン ElasticSearch Server"); assertThat(resultBooks.get(2).getTitle()) .isEqualTo("Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築"); } }
CacheおよびRegionの作成後、データをRegionに登録します。
そして、クエリを投げるためにCacheからQueryServiceを取得して、QueryService#newQueryでOQLを発行することができます。
QueryService queryService = cache.getQueryService();
Query query = queryService.newQuery("SELECT * FROM /bookRegion ORDER BY price ASC");
いきなりORDER BYとか使っていますが、かなりSQLっぽい感じですね。
FROMの部分は、「/Region名」みたいですね。
結果は、SelectResultsという形で取得します。
SelectResults results = (SelectResults) query.execute();
assertThat(results)
.hasSize(3);
SelectResultsインターフェースは、Collectionを拡張したものになっています。
SelectResults#asListで、Listとしても受けられます。
List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発"); assertThat(resultBooks.get(1).getTitle()) .isEqualTo("高速スケーラブル検索エンジン ElasticSearch Server"); assertThat(resultBooks.get(2).getTitle()) .isEqualTo("Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築");
asSetなんてものもあるようです。
基本は、こんな感じ?あとはいくつかバリエーションを見ていってみましょう。
以下の部分を埋めていく形で書いていきます。
try (Cache cache = new CacheFactory().create()) { Region<String, Book> region = cache.<String, Book>createRegionFactory(RegionShortcut.PARTITION).create("bookRegion"); getBooks().forEach(b -> region.put(b.getIsbn(), b)); // OQLのサンプル } }
OQLで使えるキーワード自体は、以下のドキュメントの「Which query language elements are supported in Geode?」の部分に書いてあります。
http://geode.docs.pivotal.io/docs/getting_started/querying_quick_reference.html
もしくは、こちら。
http://geode.docs.pivotal.io/docs/developing/query_additional/supported_keywords.html
Alias
Regionに対して、別名付与。
QueryService queryService = cache.getQueryService(); Query query = queryService.newQuery("SELECT * FROM /bookRegion b ORDER BY b.price ASC"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(3); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発"); assertThat(resultBooks.get(1).getTitle()) .isEqualTo("高速スケーラブル検索エンジン ElasticSearch Server"); assertThat(resultBooks.get(2).getTitle()) .isEqualTo("Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築");
bookRegionに対して、bという別名を与えました。
WHERE
WHERE句の利用。等値、否定、LIKEやINなどといったものが使えるようです。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT * FROM /bookRegion WHERE price < 3000 AND title LIKE '%Spring%'"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
LIMIT
LIMITで、取得件数の制限。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT * FROM /bookRegion WHERE price > 2500 ORDER BY price ASC LIMIT 1"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
メソッド呼び出し
なんと、メソッド呼び出しもできるようです。
今回はパラメーター付きですが、パラメーターがないメソッド呼び出しの場合は()はなくてもいいみたいです。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT * FROM /bookRegion WHERE title.contains('Spring')"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
COUNT
COUNTもできます。この場合は、レコードの戻り値がIntegerとなります。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT COUNT(*) FROM /bookRegion WHERE price > 3000"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Integer> count = results.asList(); assertThat(count.get(0)) .isEqualTo(2);
ネストしたクエリとJOIN
なんと、クエリのネストやJOINも書けます。Regionをテーブル的に扱う感じになりますね。
今回は、複数のエンティティは用意しなかったので、自分自身でやりますが…。
ネストしたクエリ。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT * FROM (SELECT * FROM /bookRegion) ORDER BY price ASC LIMIT 1"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
JOIN。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT * FROM /bookRegion b1, (SELECT * FROM /bookRegion b2 " + "" + "WHERE b2.isbn = '978-4777518654') b2 WHERE b1.isbn = b2.isbn"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Struct> resultBooks = results.asList(); assertThat(((Book) resultBooks.get(0).get("b1")).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発"); assertThat(((Book) resultBooks.get(0).get("b2")).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
JOINの場合、戻り値の型がStructというものになります。
JOINした時の別名でgetすると、それぞれのエンティティが取得できる感じみたいです。
List<Struct> resultBooks = results.asList(); assertThat(((Book) resultBooks.get(0).get("b1")).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発"); assertThat(((Book) resultBooks.get(0).get("b2")).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
Projection
プロパティを指定しての、Projectionも可能です。
単一のプロパティを指定した場合は、そのプロパティの型で返ってきます。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT title FROM /bookRegion WHERE isbn = '978-4777518654'"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0)) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
プロパティを複数指定した場合は、Structとして返ってきます。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT title, price FROM /bookRegion WHERE isbn = '978-4777518654'"); SelectResults results = (SelectResults) query.execute(); assertThat(results) .hasSize(1); List<Struct> resultBooks = results.asList(); Struct record = resultBooks.get(0); assertThat(record.get("title")) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発"); assertThat(record.get("price")) .isEqualTo(2700);
バインド変数を使う
いわゆるバインド変数も使えるみたいです。「$n」で指定します。1オリジンです。
QueryService queryService = cache.getQueryService(); Query query = queryService .newQuery("SELECT * FROM /bookRegion WHERE price < $1 AND title LIKE $2"); Object[] params = {3000, "%Spring%"}; SelectResults results = (SelectResults) query.execute(params); assertThat(results) .hasSize(1); List<Book> resultBooks = results.asList(); assertThat(resultBooks.get(0).getTitle()) .isEqualTo("はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発");
変数に適用するパラメーターは、Objectの配列として与えます。
まとめ
Apache GeodeのOQLを試してみました。かなりSQLライクなので、そう違和感なく入れそうで良いですね。
まあ、微妙にSQLではないところが引っかかるのかもしれませんが…。
こちらのページに載っているのもので、今回試さなかったもの(SETやkeys、values、DISTINCTなど)、
http://geode.docs.pivotal.io/docs/getting_started/querying_quick_reference.html
またインデックスも作成できたりするようなので、テーマとしては面白そうです。