Hibernate Searchの紹介

1. 概要

この記事では、Hibernate Searchの基本とその構成方法について説明し、いくつかの簡単なクエリを実装します。

全文検索機能を実装する必要があるときはいつでも、すでに精通しているツールを使用することは常にプラスです。

ORMにHibernateとJPAをすでに使用している場合は、HibernateSearchからわずか1歩です。

Hibernate Search integrates Apache Lucene, a high-performance and extensible full-text search-engine library written in Java。 これにより、LuceneのパワーとHibernateおよびJPAのシンプルさが組み合わされます。

簡単に言えば、ドメインクラスにいくつかの注釈を追加するだけで、the tool will take care of the things like database/index synchronization.

Hibernate Searchは、Elasticsearchの統合も提供します。ただし、まだ実験段階であるため、ここではLuceneに焦点を当てます。

3. 構成

3.1. Mavenの依存関係

開始する前に、まず必要なdependenciespom.xmlに追加する必要があります。


    org.hibernate
    hibernate-search-orm
    5.8.2.Final

簡単にするために、データベースとしてH2を使用します。


    com.h2database
    h2
    1.4.196

3.2. 構成

Luceneがインデックスを格納する場所も指定する必要があります。

これは、プロパティhibernate.search.default.directory_providerを介して実行できます。

filesystemを選択します。これは、ユースケースで最も簡単なオプションです。 その他のオプションはofficial documentationにリストされています。 Filesystem-master/filesystem-slave and infinispan are noteworthy for clustered applications, where the index has to be synchronized between nodes.

また、インデックスが保存されるデフォルトのベースディレクトリを定義する必要があります。

hibernate.search.default.directory_provider = filesystem
hibernate.search.default.indexBase = /data/index/default

4. モデルクラス

構成が完了すると、モデルを指定する準備が整います。

On top of the JPA annotations @Entity and @Table, we have to add an @Indexed annotation.エンティティProductにインデックスを付けることをHibernateSearchに通知します。

After that, we have to define the required attributes as searchable by adding a @Field annotation

@Entity
@Indexed
@Table(name = "product")
public class Product {

    @Id
    private int id;

    @Field(termVector = TermVector.YES)
    private String productName;

    @Field(termVector = TermVector.YES)
    private String description;

    @Field
    private int memory;

    // getters, setters, and constructors
}

termVector = TermVector.YES属性は、後で「MoreLikeThis」クエリに必要になります。

5. Luceneインデックスの構築

実際のクエリを開始する前に、we have to trigger Lucene to build the index initially

FullTextEntityManager fullTextEntityManager
  = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();

After this initial build, Hibernate Search will take care of keeping the index up to date。 I. e. 通常どおり、EntityManagerを使用してエンティティを作成、操作、および削除できます。

注:we have to make sure that entities are fully committed to the database before they can be discovered and indexed by Lucene(ちなみに、これは、example code test casesにインポートされる最初のテストデータが@Commitで注釈が付けられた専用のJUnitテストケースに含まれる理由でもあります)。

6. クエリの作成と実行

これで、最初のクエリを作成する準備が整いました。

次のセクションでは、we’ll show the general workflow for preparing and executing a query.

その後、最も重要なクエリタイプのクエリ例をいくつか作成します。

6.1. クエリを作成および実行するための一般的なワークフロー

Preparing and executing a query in general consists of four steps

ステップ1では、JPAFullTextEntityManagerを取得し、そこからQueryBuilderを取得する必要があります。

FullTextEntityManager fullTextEntityManager
  = Search.getFullTextEntityManager(entityManager);

QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()
  .buildQueryBuilder()
  .forEntity(Product.class)
  .get();

ステップ2では、HibernateクエリDSLを介してLuceneクエリを作成します。

org.apache.lucene.search.Query query = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();

ステップ3では、LuceneクエリをHibernateクエリにラップします。

org.hibernate.search.jpa.FullTextQuery jpaQuery
  = fullTextEntityManager.createFullTextQuery(query, Product.class);

最後に、ステップ4でクエリを実行します。

List results = jpaQuery.getResultList();

Note:デフォルトでは、Luceneは結果を関連性でソートします。

手順1、3、4はすべてのクエリタイプで同じです。

以下では、ステップ2に焦点を当てます。 e. さまざまな種類のクエリを作成する方法。

6.2. キーワードクエリ

最も基本的なユースケースはsearching for a specific wordです。

これは、前のセクションで実際に行ったことです。

Query keywordQuery = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();

ここで、keyword()は1つの特定の単語を検索することを指定し、onField()はLuceneにどこを検索するかを指示し、matching()は何を検索するかを指定します。

6.3. ファジークエリ

ファジークエリはキーワードクエリと同じように機能しますが、we can define a limit of “fuzziness”がそれを超えると、Luceneは2つの用語を一致として受け入れるものとします。

withEditDistanceUpTo()we can define how much a term may deviate from the other。 これは0、1、および2に設定でき、デフォルト値は2です(note:この制限はLuceneの実装によるものです)。

withPrefixLength()によって、あいまいさによって無視されるプレフィックスの長さを定義できます。

Query fuzzyQuery = queryBuilder
  .keyword()
  .fuzzy()
  .withEditDistanceUpTo(2)
  .withPrefixLength(0)
  .onField("productName")
  .matching("iPhaen")
  .createQuery();

6.4. ワイルドカードクエリ

Hibernate Searchでは、ワイルドカードクエリを実行することもできます。 e. 単語の一部が不明なクエリ。

このために、単一の文字に「?”」を使用し、任意の文字シーケンスに「*”」を使用できます。

Query wildcardQuery = queryBuilder
  .keyword()
  .wildcard()
  .onField("productName")
  .matching("Z*")
  .createQuery();

6.5. フレーズクエリ

複数の単語を検索する場合は、フレーズクエリを使用できます。 必要に応じて、phrase()withSlop()を使用して、for exact or for approximate sentencesを調べることができます。 スロップ係数は、文で許可される他の単語の数を定義します。

Query phraseQuery = queryBuilder
  .phrase()
  .withSlop(1)
  .onField("description")
  .sentence("with wireless charging")
  .createQuery();

6.6. 単純なクエリ文字列クエリ

以前のクエリタイプでは、クエリタイプを明示的に指定する必要がありました。

ユーザーにさらに力を与えたい場合は、単純なクエリ文字列クエリby that, he can define his own queries at runtimeを使用できます。

次のクエリタイプがサポートされています。

  • ブール値(および“â€を使用、または“|â€を使用、“-“を使用しない)

  • プレフィックス(prefix *)

  • フレーズ(“somephraseâ€)

  • 優先順位(括弧を使用)

  • ファジー(fuzy〜2)

  • フレーズ照会のニア演算子(「一部のフレーズ」〜3)

次の例では、ファジー、フレーズ、ブールのクエリを組み合わせます。

Query simpleQueryStringQuery = queryBuilder
  .simpleQueryString()
  .onFields("productName", "description")
  .matching("Aple~2 + \"iPhone X\" + (256 | 128)")
  .createQuery();

6.7. 範囲クエリ

Range queries search for avalue in between given boundaries。 これは、数値、日付、タイムスタンプ、および文字列に適用できます。

Query rangeQuery = queryBuilder
  .range()
  .onField("memory")
  .from(64).to(256)
  .createQuery();

6.8. このクエリのように

最後のクエリタイプは「More Like This」–クエリです。 このために、エンティティとHibernate Search returns a list with similar entitiesを提供し、それぞれに類似度スコアを付けます。

前述のように、この場合、モデルクラスのtermVector = TermVector.YES属性が必要です。これは、インデックス作成中に各用語の頻度を格納するようにLuceneに指示します。

これに基づいて、クエリの実行時に類似性が計算されます。

Query moreLikeThisQuery = queryBuilder
  .moreLikeThis()
  .comparingField("productName").boostedTo(10f)
  .andField("description").boostedTo(1f)
  .toEntity(entity)
  .createQuery();
List results = (List) fullTextEntityManager
  .createFullTextQuery(moreLikeThisQuery, Product.class)
  .setProjection(ProjectionConstants.THIS, ProjectionConstants.SCORE)
  .getResultList();

6.9. 複数のフィールドを検索する

これまで、onField()を使用して、1つの属性を検索するためのクエリのみを作成していました。

ユースケースに応じて、we can also search two or more attributes

Query luceneQuery = queryBuilder
  .keyword()
  .onFields("productName", "description")
  .matching(text)
  .createQuery();

さらに、we can specify each attribute to be searched separately、e。 g. 1つの属性にブーストを定義する場合:

Query moreLikeThisQuery = queryBuilder
  .moreLikeThis()
  .comparingField("productName").boostedTo(10f)
  .andField("description").boostedTo(1f)
  .toEntity(entity)
  .createQuery();

6.10. クエリの結合

最後に、Hibernate Searchはさまざまな戦略を使用したクエリの結合もサポートしています。

  • SHOULD:クエリには、サブクエリの一致する要素が含まれている必要があります

  • MUST:クエリには、サブクエリの一致する要素が含まれている必要があります

  • MUST NOT:クエリにサブクエリの一致する要素を含めることはできません

集計はsimilar to the boolean ones AND, OR and NOT.です。ただし、名前は異なり、関連性にも影響を与えることを強調しています。

たとえば、2つのクエリ間のSHOULDは、2つのクエリの1つに一致する場合、ブール値のOR:に似ていますが、この一致が返されます。

ただし、両方のクエリが一致する場合、1つのクエリのみが一致する場合と比較して、一致の関連性が高くなります。

Query combinedQuery = queryBuilder
  .bool()
  .must(queryBuilder.keyword()
    .onField("productName").matching("apple")
    .createQuery())
  .must(queryBuilder.range()
    .onField("memory").from(64).to(256)
    .createQuery())
  .should(queryBuilder.phrase()
    .onField("description").sentence("face id")
    .createQuery())
  .must(queryBuilder.keyword()
    .onField("productName").matching("samsung")
    .createQuery())
  .not()
  .createQuery();

7. 結論

この記事では、Hibernate Searchの基本について説明し、最も重要なクエリタイプを実装する方法を示しました。 より高度なトピックは、official documentationにあります。

いつものように、例の完全なソースコードはover on GitHubで入手できます。