CLOVER🍀

That was when it all began.

Apache Geodeの検索機能を使う(OQLを使う)

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アプリ開発");

メソッド呼び出し

なんと、メソッド呼び出しもできるようです。

Method Invocations

今回はパラメーター付きですが、パラメーターがないメソッド呼び出しの場合は()はなくてもいいみたいです。

            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

またインデックスも作成できたりするようなので、テーマとしては面白そうです。