CLOVER🍀

That was when it all began.

Hibernate Searchで保存したインデックスを、インデックスブラウザ「Luke」で見る

Luceneのインデックスを見るツールとして、「Luke」というインデックスブラウザがあります。

Luke - Lucene Index Toolbox
https://code.google.com/p/luke/

しかし、オリジナルのLukeはLucene 4系には対応しておらず、ここからforkされたものが存在するようです。

https://github.com/tarzanek/luke

今回、こちらのLukeを使用して、Hibernate Searchで保存したLuceneのインデックスを見てみようと思います。

Lukeのビルドと起動

forkされたLukeを使うには、自分でビルドする必要があります。今回使用しているLukeでは、Lucene 4.10.3を使用しているようです。

GitHubからCloneして

$ git clone https://github.com/tarzanek/luke.git

今のLukeだと、なぜかディレクトリの作成が必要です…。

$ cd luke/
$ mkdir lib/solr

そして、ant!

$ ant

「BUILD SUCCESSFUL」と表示されれば、ビルドは成功です。

起動するには、生成された「lukeall-4.10.3.jar」を指定して実行します。

$ java -jar dist/lukeall-4.10.3.jar

起動した画面。

これで、Lukeを使う用意はできました。

インデックスの登録

Lukeを起動しても、見るべきインデックスがなければどうにもなりません。

というわけで、Luceneのインデックスを作成します。Hibernat Searchを使って。

Maven依存関係。

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.3.8.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search-orm</artifactId>
      <version>5.0.1.Final</version>
    </dependency>
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-kuromoji</artifactId>
      <version>4.10.3</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.34</version>
      <scope>runtime</scope>
    </dependency>

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
  <persistence-unit name="search.pu" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>org.littlewings.hibernate.luke.Book</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/practice?useUnicode=true&amp;characterEncoding=utf-8&amp;characterSetResults=utf-8&amp;useServerPrepStmts=true&amp;useLocalSessionState=true&amp;elideSetAutoCommits=true&amp;alwaysSendSetIsolation=false" />
      <property name="javax.persistence.jdbc.user" value="kazuhira" />
      <property name="javax.persistence.jdbc.password" value="password" />

      <!-- Hibernate -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
      <property name="hibernate.show_sql" value="true" />
      <property name="hibernate.format_sql" value="true" />

      <!-- Hibernate Search -->
      <property name="hibernate.search.default.directory_provider" value="filesystem" />
      <property name="hibernate.search.default.indexBase" value="indexes" />
      <property name="hibernate.search.analyzer" value="org.apache.lucene.analysis.ja.JapaneseAnalyzer" />
      <property name="hibernate.search.lucene_version" value="LUCENE_4_10_3" />
    </properties>
  </persistence-unit>
</persistence>

インデックスは、ファイルシステムに保存します。保存先は、起動ディレクトリ配下に、「indexes」ディレクトリを設けてこの配下に保存するものとします。

Analyzerは、Kuromojiの「org.apache.lucene.analysis.ja.JapaneseAnalyzer」としました。

Entityクラス

package org.littlewings.hibernate.luke;

import javax.persistence.*;

import org.hibernate.search.annotations.*;
import org.hibernate.search.bridge.builtin.*;

@Entity
@Table(name = "book")
@Indexed
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    @Field(analyze = Analyze.NO)
    private String isbn;

    @Column
    @Field
    private String title;

    @Column
    @Field
    private Integer price;

    @Column
    @Field
    private String summary;

    public Book() { }

    public Book(String isbn, String title, Integer price, String summary) {
        this.isbn = isbn;
        this.title = title;
        this.price = price;
        this.summary = summary;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getIsbn() { return isbn; }
    public void seIsbn(String isbn) { this.isbn = isbn; }
    public Integer getPrice() { return price; }
    public void setPrice(Integer price) { this.price = price; }
    public String getSummary() { return summary; }
    public void setSummary(String summary) { this.summary = summary; }
}

テーマは書籍、まあ普通です。

あとは、インデックスを作成するプログラムを作成。

package org.littlewings.hibernate.luke;

import java.util.*;

import javax.persistence.*;

public class App {
    public static void main(String... args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("search.pu");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        Arrays
            .asList(new Book("978-4777518654",
                             "はじめてのSpring Boot 「Spring Framework」で簡単Javaアプリ開発",
                             2700,
                             "新世代Javaフレームワーク"),
                    new Book("978-4798124605",
                             "Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava",
                             4536,
                             "新バージョンの革新性を取り上げ、さまざまな仕様と、アプリケーションを開発するためにそれらの仕様を組み立てる方法を詳しく解説。"),
                    new Book("978-4048662024",
                             "高速スケーラブル検索エンジン ElasticSearch Server",
                             3024,
                             "高速、スケーラブル、柔軟性のある検索ソリューションを実現!"),
                    new Book("978-4774161631",
                             "[改訂新版] Apache Solr入門 〜オープンソース全文検索エンジン",
                             3888,
                             "Solr4対応、検索エンジンの仕組み解説「スキーマ定義」「インデックス作成」「検索」、XML/JSONフロントエンドプログラミング、レコメンデーション、ManifoldCF、SolrCloud。"))
            .stream()
            .forEach(em::persist);

        tx.commit();

        em.close();
        emf.close();

        System.exit(0);
    }
}

で、実行。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.hibernate.luke.App

実行が完了すると、カレントディレクトリ配下に「indexes」というディレクトリおよび、EntityクラスのFQCNを持ったディレクトリが作成されています。この中に、Luceneのインデックスが保存されています。

$ ll indexes/org.littlewings.hibernate.luke.Book
合計 28
drwxrwxr-x 2 xxxxx xxxxx 4096  1月 20 00:04 ./
drwxrwxr-x 3 xxxxx xxxxx 4096  1月 20 00:04 ../
-rw-rw-r-- 1 xxxxx xxxxx  284  1月 20 00:04 _0.cfe
-rw-rw-r-- 1 xxxxx xxxxx 3127  1月 20 00:04 _0.cfs
-rw-rw-r-- 1 xxxxx xxxxx  248  1月 20 00:04 _0.si
-rw-rw-r-- 1 xxxxx xxxxx   36  1月 20 00:04 segments.gen
-rw-rw-r-- 1 xxxxx xxxxx  102  1月 20 00:04 segments_2
-rw-rw-r-- 1 xxxxx xxxxx    0  1月 20 00:04 write.lock

これで、準備は完了です。

Lukeでインデックスを見てみる

それでは、先ほどビルドしたLukeで、作成したインデックスを見てみましょう。

起動したLukeの「Path」と書かれたテキストフィールドに、インデックスが保存されたディレクトリを指定します。

「OK」をクリック。

すると、「Overview」タブが開き、保存されたドキュメントの数やトークンの情報などを見ることができます。

※数字のフィールド「price」が妙な表示になっているのは、なんとなく理由はわかりますが…そのうち書くかもです

「Document」タブでは、(Luceneの)Documentのidやトークンを指定してドキュメントを見ることができます。

本来は値も表示されて欲しいところですが、Hibernate Searchでは値を保存しないので、このような状態になっているのだと思います。保存したい場合は、@FieldアノテーションでStore.YESにする必要があるはず。

「Search」タブでは、QueryParserでパースできるクエリを入力して検索することができます。使用するAnalyzerは、右側で設定します。

ところで、ここで選択できるAnalyzerは、Lukeの起動時にクラスパスに入っているものが対象のようで、KuromojiのJapaneseAnalyzerはリストに表れません。

Lukeの「Search」タブでKuromojiのJapaneseAnalyzerを選択できるようにする

というわけで、せっかくなのでLuke上でKuromojiのJapaneseAnalyzerが選べるようにしてみましょう。

なんのことはなく、Lucene-KuromojiのJARをダウンロードしてきて、どこか適当なところに置きます。

今回はMavenローカルリポジトリから、Lukeのdistディレクトリに放り込みました。

$ cp ~/.m2/repository/org/apache/lucene/lucene-analyzers-kuromoji/4.10.3/lucene-analyzers-kuromoji-4.10.3.jar dist/

ちょっと乱暴ですが、ワイルドカードでクラスパスに全部突っ込みつつ、今度はメインクラス「org.getopt.luke.Luke」を指定して起動します。

$ java -cp 'dist/*:dist/hadoop/*:dist/solr/*' org.getopt.luke.Luke

すると、Luke上でJapaneseAnalyzerが使えるようになっています。

まあ、実際に使うAnalyzerはEntityで定義したものと異なることが多いでしょうし、アナライズする/しないの設定もあるので「Search」の部分をそれほど使うこともない気はしますが…。

とはいえ、インデックスにどのように保存されているのかが見れるのは、知っておくと便利なのでは?と思います。