第16章 ドメインオブジェクトのインデックス構造へのマッピング

16.1. 基本のマッピング

16.1.1. 基本のマッピング

Red Hat JBoss Data Grid では、すべての @Indexed オブジェクトの識別子は値の保存に使用されるキーになります。キーがインデックス化される方法は、@Transformable@ProvidedId、カスタムタイプ、およびカスタム FieldBridge 実装の組み合わせを使用するとカスタマイズできます。

@DocumentId 識別子は JBoss Data Grid の値には適用されません。

Lucene ベースの Query API は、以下の一般的なアノテーションを使用してエンティティーをマップします。

  • @Indexed
  • @Field
  • @NumericField

16.1.2. @Indexed

@Indexed アノテーションはキャッシュされたエントリーをインデックス可能であると宣言します。@Indexed アノテーションが付いてないエントリーはすべて無視されます。

@Indexed でクラスをインデックス可能にする

@Indexed
public class Essay {
}

任意で @Indexed アノテーションの index 属性を指定して、インデックスのデフォルト名を変更することもできます。

16.1.3. @Field

エンティティーの各プロパティーまたは属性をインデックス化できます。プロパティーや属性はデフォルトではアノテーションが付けられていないため、インデックス化の処理では無視されます。@Field アノテーションはプロパティーをインデックス化されたプロパティーとして宣言し、以下の属性を 1 つ以上設定するとインデックス化処理の内容を設定できます。

name
この名前でプロパティーが Lucene Document に保存されます。デフォルトでは JavaBeans の慣例に準拠し、この属性はプロパティー名と同じになります。
store

プロパティーが Lucene インデックスに保存されるかどうかを指定します。プロパティーが保存されると、Lucene Document から元の値で読み出すことができます。これは、要素のインデックス化の有無には関係ありません。有効なオプションは次のとおりです。

  • Store.YES: より多くのインデックス領域を消費しますが、射影を許可します。射影 を参照してください。
  • Store.COMPRESS: プロパティーを圧縮して格納します。この属性は CPU をより消費します。
  • Store.NO: ストレージはありません。これは store 属性のデフォルト設定になります。
index

プロパティーがインデックス化されたかどうかを示します。以下の値を適用できます。

  • Index.NO: インデックス化は適用されません。クエリーで見つけることができません。この設定は検索可能である必要がなく、射影可能なプロパティーに使用されます。
  • Index.YES: 要素はインデックス化され検索可能です。これは index 属性のデフォルト設定になります。
analyze

プロパティーが分析されるかどうかを判断します。analize 属性は、そのコンテンツでプロパティーを検索できるようにします。たとえば、テキストフィールドを分析する必要があっても日付フィールドは分析する必要がない場合があります。以下を使用して analyze 属性を有効または無効にします。

  • Analyze.YES
  • Analyze.NO

analyze 属性はデフォルトで有効になっています。Analyze.YES 設定では、Index.YES 属性でプロパティーがインデックス化される必要があります。

以下の属性はソートに使用され、分析してはいけません。

norms

インデックス時間のブースティング情報を格納するかどうかを判断します。有効な設定は次のとおりです。

  • Norms.YES
  • Norms.NO

この属性のデフォルトは Norms.YES です。norms を無効にするとメモリーを節約できますが、インデックス時のブースティング情報は利用できません。

termVector

単語 (term) と出現頻度 (frequency) のペアを示します。この属性を使用すると、インデックス化中にドキュメント内で term vector を保存できます。デフォルト値は TermVector.NO です。この属性に使用できる設定は次のとおりです。

  • TermVector.YES: 各ドキュメントに term vector を保存します。これにより、同期された 2 つのアレイが生成されます (1 つのアレイには document term が含まれ、もう 1 つのアレイには term の頻度が含まれます)。
  • TermVector.NO: term vector を保存しません。
  • TermVector.WITH_OFFSETS: term vector およびトークンオフセット情報を保存します。これは、TermVector.YES と同じですが、言葉の開始および終了オフセット位置情報が含まれます。
  • TermVector.WITH_POSITIONS: term vector およびトークン位置情報を保存します。これは、TermVector.YES と同じですが、ドキュメントで言葉が発生する順序位置 (ordinal position) が含まれます。
  • TermVector.WITH_POSITION_OFFSETS: term vector、トークン位置、およびオフセット情報を保存します。これは YESWITH_OFFSETS、および WITH_POSITIONS の組み合わせです。
indexNullAs

デフォルトでは null 値は無視され、インデックス化されません。しかし、indexNullAs を使用すると、文字列が null 値のトークンとして挿入されるよう指定することができます。indexNullAs パラメーターを使用する場合、検索クエリーで同じトークンを使用して null 値を検索します。この機能は Analyze.NO と使用する必要があります。この属性の有効な設定は次のとおりです。

  • Field.DO_NOT_INDEX_NULL: この属性のデフォルト値です。この設定は null 値がインデックス化されないことを示します。
  • Field.DEFAULT_NULL_TOKEN: デフォルトの null トークンが使用されることを示します。このデフォルトの null トークンは、default_null_token プロパティーを使用して指定できます。このプロパティーが設定されずに Field.DEFAULT_NULL_TOKEN が指定された場合、デフォルトで null という文字列が使用されます。
警告

カスタムの FieldBridge または TwoWayFieldBridge を実装する場合、null 値のインデックス化の処理は開発者に委ねられます (LuceneOptions.indexNullAs() の JavaDocs を参照してください)。

16.1.4. @NumericField

@NumericField アノテーションは @Field と同じ範囲で指定できます。

@NumericField アノテーションは、Integer、Long、Float、および Double プロパティーに対して指定できます。インデックス化するときにトライ木の構造を使用して値がインデックス化されます。プロパティーが数値のフィールドとしてインデックス化されると、効率的な範囲のクエリーおよびソートが有効になり、標準の @Field プロパティーで同じクエリーを実行するよりも高速に順序付けできます。@NumericField アノテーションは以下の任意のパラメーターを許可します。

  • forField: 数値としてインデックス化される関連する @Field の名前を指定します。プロパティーに 1 つの @Field 宣言以外が含まれる場合は必須になります。
  • precisionStep: トライ木構造がインデックスに保存される方法を変更します。precisionSteps を小さくするとディスク領域の使用量が増え、範囲およびソートクエリーが高速になります。値を大きくすると使用される領域が少なくなり、範囲クエリーのパフォーマンスが通常の @Fields の範囲クエリーに近くなります。precisionStep のデフォルト値は 4 です。

@NumericField は、DoubleLongInteger、および Float のみをサポートします。他の数値型に対して Lucene の同様の機能を使用できないため、他の型はデフォルトまたはカスタムの TwoWayFieldBridge を用いた文字列のエンコーディングを使用する必要があります。

カスタムの NumericFieldBridge を使用することもできます。カスタム設定は、型の変換中に近似が必要になります。以下は、カスタム NumericFieldBridge を定義する例になります。

カスタム NumericFieldBridge の定義

public class BigDecimalNumericFieldBridge extends NumericFieldBridge {
    private static final BigDecimal storeFactor = BigDecimal.valueOf(100);

    @Override
    public void set(String name,
                    Object value,
                    Document document,
                    LuceneOptions luceneOptions) {
        if (value != null) {
            BigDecimal decimalValue = (BigDecimal) value;
            Long indexedValue = Long.valueOf(
                decimalValue
                .multiply(storeFactor)
                .longValue());
            luceneOptions.addNumericFieldToDocument(name, indexedValue, document);
        }
    }

    @Override
    public Object get(String name, Document document) {
        String fromLucene = document.get(name);
        BigDecimal storedBigDecimal = new BigDecimal(fromLucene);
        return storedBigDecimal.divide(storeFactor);
    }
}

16.2. プロパティーを複数回マッピング

異なるインデックス化ストラテジーを使用して、プロパティーをインデックスごとに複数回マッピングする必要があることがあります。たとえば、フィールドでクエリーをソートするには、フィールドが分析されていない必要があります。このプロパティーで単語を基に検索し、ソートも行うには、このプロパティーを 2 回インデックス化する必要があります (分析する回と分析しない回)。@Fields を使用してこの検索を実行できます。例を以下に示します。

@Fields を使用してプロパティーを複数回マッピング

@Indexed(index = "Book")
public class Book {
    @Fields( {
        @Field,
        @Field(name = "summary_forSort", analyze = Analyze.NO, store = Store.YES)
    })
    public String getSummary() {
        return summary;
    }
}

上記の例では、summary が 2 回インデックス化されます。summary としてトークン化される方法で 1 度インデックス化され、summary_forSort として非トークン化される方法でもう 1 度インデックス化されます。@Field が使用される場合、@Fields は 2 つの属性をサポートします。

  • analyzer: プロパティーごとではなくフィールドごとに @Analyzer アノテーションを定義します。
  • bridge: プロパティーごとではなくフィールドごとに @FieldBridge アノテーションを定義します。

16.3. 埋め込みオブジェクトおよび関連するオブジェクト

16.3.1. 埋め込みオブジェクトおよび関連するオブジェクト

関連するオブジェクトと埋め込みオブジェクトはルートエンティティーインデックスの一部としてインデックス化できます。これにより、関連するオブジェクトのプロパティーを基にしたエンティティーの検索が可能になります。

16.3.2. 関連するオブジェクトのインデックス化

以下の例の目的は、Lucene クエリー address.city:Atlanta を使用して、関連する市がアトランタである場所を返すことです。場所フィールドは Place インデックスでインデックス化されます。Place インデックスドキュメントには以下のフィールドも含まれます。

  • address.street
  • address.city

これらのフィールドはクエリーも可能です。

関連付けのインデックス化

@Indexed
public class Place {

    @Field
    private String name;

    @IndexedEmbedded
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private Address address;
}

public class Address {

    @Field
    private String street;

    @Field
    private String city;

    @ContainedIn
    @OneToMany(mappedBy = "address")
    private Set<Place> places;
}

16.3.3. @IndexedEmbedded

@IndexedEmbedded を使用する場合、データは Lucene インデックスで非正規化されます。そのため、Place および Address オブジェクトの変更を反映するために Lucene ベースの Query API を更新し、インデックスを最新の状態にする必要があります。Address の変更時に Lucene ドキュメントが確実に更新されるようにするため、双方向の関係の逆側に @ContainedIn を付けます。@ContainedIn は、エンティティーを示す関連付けと埋め込みオブジェクトを示す関連付けの両方に使用できます。

@IndexedEmbedded アノテーションは入れ子にすることができます。属性には @IndexedEmbedded アノテーションを付けることができます。関連するクラスの属性はメインエントリーインデックスに追加されます。下記の例では、インデックスに以下のフィールドが含まれます。

  • name
  • address.street
  • address.city
  • address.ownedBy_name

入れ子の @IndexedEmbedded および @ContainedIn の使用法

@Indexed
public class Place {
    @Field
    private String name;

    @IndexedEmbedded
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private Address address;
}

public class Address {
    @Field
    private String street;

    @Field
    private String city;

    @IndexedEmbedded(depth = 1, prefix = "ownedBy_")
    private Owner ownedBy;

    @ContainedIn
    @OneToMany(mappedBy = "address")
    private Set<Place> places;
}

public class Owner {
    @Field
    private String name;
}

デフォルトの接頭辞は propertyName. で、従来のオブジェクトナビゲーションの慣例に従います。これをオーバーライドするには ownedBy プロパティーで示されたように prefix 属性を使用します。

注記

接頭辞を空の文字列に設定することはできません。

オブジェクトグラフにクラスの循環依存関係が含まれる場合、depth プロパティーが使用されます。たとえば、OwnerPlace を示す場合がこれに該当します。予期された深さまたはオブジェクトグラフの境界に達した後、クエリーモジュールは属性の追加を停止します。自己参照クラスは循環依存関係の例の 1 つになります。この例では、深さが 1 に設定されているため、Owner@IndexedEmbedded 属性はすべて無視されます。

オブジェクトの関連付けに @IndexedEmbedded を使用すると、Lucene のクエリー構文を使用してクエリーを表現できます。例を以下に示します。

  • 名前に JBoss が含まれ、住所の市が Atlanta である場所を返す場合、Lucene クエリーでは次のようになります。

    +name:jboss +address.city:atlanta
  • 名前に JBoss が含まれ、所有者の名前に Joe が含まれる場所を返す場合、Lucene クエリーでは次のようになります。

    +name:jboss +address.ownedBy_name:joe

この操作は関係結合操作と似ていますが、データが重複されません。そのままの状態では、Lucene インデックスには関連付けの観念がなく、結合操作は存在しません。完全テキストのインデックス速度や機能充実の利点を活かしながら正規化されたリレーショナルモデルを維持すると有用であることがあります。

関連したオブジェクトは @Indexed であることもあります。@IndexedEmbedded がエンティティーを示す場合、関連付けは指向性を持つ必要があり、逆側に @ContainedIn アノテーションを付ける必要があります。このアノテーションを付けないと、関連したエンティティーが更新されたときに Lucene ベースのクエリー API はルートインデックスを更新できません。この例では、 関連する Address インスタンスが更新されると Place インデックスドキュメントが更新されます。

16.3.4. targetElement プロパティー

targetElement パラメーターを使用すると目的のオブジェクト型をオーバーライドできます。 このメソッドは、@IndexedEmbedded アノテーションが付けられたオブジェクト型がデータグリッドおよび Lucene ベースの Query API が目的とするオブジェクト型ではない場合に使用されます。これは、実装の代わりにインターフェースが使用されると発生します。

@IndexedEmbeddedtargetElement プロパティーの使用

@Indexed
public class Address {

    @Field
    private String street;

    @IndexedEmbedded(depth = 1, prefix = "ownedBy_", targetElement = Owner.class)
    private Person ownedBy;

    ...
}

public class Owner implements Person { ... }

16.4. ブースティング

16.4.1. ブースティング

Lucene はブースティングを使用して特定のフィールドやドキュメントの重要度を上げます。Lucene はインデックス時のブースティングと検索時のブースティングを区別します。

16.4.2. 静的なインデックス時ブースティング

インデックス化されたクラスまたはプロパティーの静的なブースト値を定義するには、@Boost アノテーションを使用します。このアノテーションは @Field 内で使用でき、メソッドやクラスレベルで直接指定することもできます。

下記の例は以下を意味します。

  • Essay が検索リストの最上部に達する可能性が 1.7 倍になります。
  • プロパティーの @Field.boost および @Boost は累積的であるため、summary フィールドは 3.0 倍 (2 x 1.5) になり、ISBN フィールドよりも重要度が高くなります。
  • text フィールドの重要度は ISBN フィールドの 1.2 倍になります。

@Boost の異なる使用方法

@Indexed
@Boost(1.7f)
public class Essay {

    @Field(name = "Abstract", store=Store.YES, boost = @Boost(2f))
    @Boost(1.5f)
    public String getSummary() { return summary; }

    @Field(boost = @Boost(1.2f))
    public String getText() { return text; }

    @Field
    public String getISBN() { return isbn; }

}

16.4.3. 動的なインデックス時ブースティング

@Boost アノテーションは静的ブースト係数を定義します。この係数は、実行時のインデックス化されたエンティティーの状態とは関係ありません。ただし、場合によってはブースト係数がエンティティーの実際の状態に依存することがあります。この場合、 @DynamicBoost アノテーションをカスタム BoostStrategy とともに使用します。

@Boost および @DynamicBoost アノテーションはエンティティーとの関連で使用することができ、定義されたブースト係数はすべて累積的です。@DynamicBoost をクラスまたはフィールドレベルで置くことができます。

以下の例では、インデックス化するときに使用される BoostStrategy インターフェースの実装として VIPBoostStrategy を指定することにより、動的なブーストがクラスレベルで定義されます。エンティティー全体が defineBoost メソッドに渡されるか、またはアノテーションが付けられたフィールド/プロパティーの値のみが渡されるかはアノテーションの配置によります。渡されたオブジェクトを正しい型にキャストする必要があります。

動的なブーストの例

public enum PersonType {
    NORMAL,
    VIP
}

@Indexed
@DynamicBoost(impl = VIPBoostStrategy.class)
public class Person {
    private PersonType type;
}

public class VIPBoostStrategy implements BoostStrategy {
    public float defineBoost(Object value) {
        Person person = (Person) value;
        if (person.getType().equals(PersonType.VIP)) {
            return 2.0f;
        }
        else {
            return 1.0f;
        }
    }
}

この例では、VIP のインデックス化された値すべての重要度は VIP でない値の 2 倍になります。

注記

指定の BoostStrategy 実装は、パブリックの引数のないコンストラクターを定義する必要があります。

16.5. 分析

16.5.1. 分析

16.5.2. デフォルトのアナライザーおよびクラス別のアナライザー

デフォルトのアナライザークラスはトークン化されたフィールドをインデックス化するために使用され、default.analyzer プロパティーを用いて設定が可能です。このプロパティーのデフォルト値は org.apache.lucene.analysis.standard.StandardAnalyzer です。

アナライザークラスはエンティティー、プロパティー、および @Field ごとに定義できます。これは、1 つのプロパティーから複数のフィールドがインデックス化されるときに便利です。

以下の例では、 EntityAnalyzer を使用して、name などのトークン化されたプロパティーがすべてインデックス化されます。例外は summary と body で、これらはそれぞれ PropertyAnalyzerFieldAnalyzer によってインデックス化されます。

@Analyzer の異なる使用方法

@Indexed
@Analyzer(impl = EntityAnalyzer.class)
public class MyEntity {

    @Field
    private String name;

    @Field
    @Analyzer(impl = PropertyAnalyzer.class)
    private String summary;

    @Field(analyzer = @Analyzer(impl = FieldAnalyzer.class))
    private String body;
}

注記

1 つのエンティティーで複数のアナライザーを使用しないようにしてください。特に QueryParser を使用する場合はクエリーの構築が複雑になり、結果の予測が難しくなります。フィールドのインデックス化とクエリーに同じアナライザーを使用してください。

16.5.3. 名前付きのアナライザー

クエリーモジュールはアナライザー定義を使用して Analyzer 関数の複雑さに対応します。アナライザー定義は、複数の @Analyzer 宣言による再使用が可能で、以下が含まれます。

  • 名前: 定義の参照に使用される一意の文字列。
  • CharFilters のリスト: 各 CharFilter はトークン化の前に入力文字を事前処理します。CharFilters は文字を追加、変更、および削除できます。一般的な使用例の 1 つが文字の正規化です。
  • Tokenizer: 入力ストリームを個別の単語にトークン化します。
  • フィルターのリスト: 各フィルターは単語を削除または変更します。また、Tokenizer によって提供されたストリームに単語を追加することもあります。

Analyzer はこれらのコンポーネントを複数のタスクに分類し、以下の手順を使用して各コンポーネントの再使用やコンポーネントの柔軟な構築を実現します。

アナライザーの処理

  1. CharFilters は文字入力を処理します。
  2. Tokenizer は文字入力をトークンに変換します。
  3. トークンは TokenFilters によって処理されます。

Lucene ベースの Query API は、Solr アナライザーフレームワークを利用してこのインフラストラクチャーをサポートします。

16.5.4. アナライザーの定義

アナライザー定義は定義後に @Analyzer アノテーションによって再使用されます。

名前によるアナライザーの参照

@Indexed
@AnalyzerDef(name = "customanalyzer")
public class Team {

    @Field
    private String name;

    @Field
    private String location;

    @Field
    @Analyzer(definition = "customanalyzer")
    private String description;
}

@AnalyzerDef によって宣言されたアナライザーインスタンスは、SearchFactory の名前でも利用できます。これは、クエリーの構築時に役立ちます。

Analyzer analyzer = Search.getSearchManager(cache).getAnalyzer("customanalyzer")

クエリーの実行時、フィールドはフィールドのインデックス化に使用されたアナライザーを使用する必要があります。クエリーとインデックス化処理で同じトークンが再使用されます。

16.5.5. Solr の @AnalyzerDef

Maven を使用する場合、必要なすべての Apache Solr 依存関係はアーティファクト org.hibernate:hibernate-search-analyzers の依存関係として定義されるようになりました。以下の依存関係を追加します。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-search-analyzers</artifactId>
    <version>${version.hibernate.search}</version>
<dependency>

以下の例では、CharFilter はそのファクトリーによって定義されます。この例では、マッピング文字フィルターが使用され、マッピングファイルに指定されたルールを基にして入力の文字列を置き換えます。この例では、専用の単語プロパティーファイルを読み取って StopFilter フィルターが構築されます。フィルターは大文字と小文字を区別しません。

@AnalyzerDef および Solr フレームワーク

  1. CharFilter の設定

    ファクトリーで CharFilter を定義します。この例では、マッピング CharFilter が使用され、マッピングファイルに指定されたルールを基にして入力の文字を置き換えます。

    @AnalyzerDef(name = "customanalyzer",
        charFilters = {
            @CharFilterDef(factory = MappingCharFilterFactory.class, params = {
                @Parameter(name = "mapping",
                    value =
                        "org/hibernate/search/test/analyzer/solr/mapping-chars.properties")
            })
        },
  2. Tokenizer の定義

    TokenizerStandardTokenizerFactory.class を使用して定義されます。

    @AnalyzerDef(name = "customanalyzer",
        charFilters = {
            @CharFilterDef(factory = MappingCharFilterFactory.class, params = {
                @Parameter(name = "mapping",
                    value =
                        "org/hibernate/search/test/analyzer/solr/mapping-chars.properties")
            })
        },
    
        tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class)
  3. フィルターのリスト

    ファクトリーでフィルターのリストを定義します。この例では、専用の単語プロパティーファイルを読み取って StopFilter フィルターが構築されます。フィルターは大文字と小文字を区別しません。

    @AnalyzerDef(name = "customanalyzer",
        charFilters = {
            @CharFilterDef(factory = MappingCharFilterFactory.class, params = {
                @Parameter(name = "mapping",
                    value =
                        "org/hibernate/search/test/analyzer/solr/mapping-chars.properties")
            })
        },
    
        tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
        filters = {
    
            @TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class),
            @TokenFilterDef(factory = LowerCaseFilterFactory.class),
            @TokenFilterDef(factory = StopFilterFactory.class, params = {
                @Parameter(name = "words",
                    value= "org/hibernate/search/test/analyzer/solr/stoplist.properties" ),
                @Parameter(name = "ignoreCase", value = "true")
            })
        })
    public class Team {
    }
注記

フィルターと CharFilters は、 @AnalyzerDef アノテーションで定義された順に適用されます。

16.5.6. アナライザーリソースのロード

TokenizersTokenFilters、および CharFiltersStopFilterFactory.class または類義語フィルターを使用して、設定ファイルやメタデータファイルなどのリソースをロードできます。仮想マシンのデフォルトを明示的に指定するには、resource_charset パラメーターを追加します。

特定の文字セットを使用したプロパティーファイルのロード

@AnalyzerDef(name = "customanalyzer",
    charFilters = {
        @CharFilterDef(factory = MappingCharFilterFactory.class, params = {
            @Parameter(name = "mapping",
                value =
                    "org/hibernate/search/test/analyzer/solr/mapping-chars.properties")
        })
    },
    tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
    filters = {
        @TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class),
        @TokenFilterDef(factory = LowerCaseFilterFactory.class),
        @TokenFilterDef(factory = StopFilterFactory.class, params = {
            @Parameter(name="words",
                value= "org/hibernate/search/test/analyzer/solr/stoplist.properties"),
            @Parameter(name = "resource_charset", value = "UTF-16BE"),
            @Parameter(name = "ignoreCase", value = "true")
        })
    })
public class Team {
}

16.5.7. 動的アナライザーの選択

クエリーモジュールは @AnalyzerDiscriminator アノテーションを使用して動的なアナライザーの選択を有効にします。

インデックス化するエンティティーの現在の状態を基にアナライザーを選択できます。これは、特に多言語のアプリケーションで役に立ちます。たとえば、BlogEntry クラスを使用する場合、アナライザーはエントリーの言語プロパティーに依存することができます。このプロパティーに応じて、テキストのインデックス化に適切な言語固有のステマーが選択されます。

Discriminator インターフェースの実装は既存のアナライザー定義の名前を返す必要がありますが、デフォルトのアナライザーがオーバーライドされない場合は null を返す必要があります。

下記の例は、言語パラメーターが de または en のいずれかで、@AnalyzerDefs で指定されることを前提とします。

@AnalyzerDiscriminator の設定

  1. 動的なアナライザーの事前定義

    @AnalyzerDiscriminator には、動的に使用されるすべてのアナライザーを @AnalyzerDef を使用して事前定義する必要があります。動的にアナライザーを選択するために、@AnalyzerDiscriminator アノテーションをクラスまたはエンティティーの特定のプロパティーに配置できます。Discriminator インターフェースの実装は、@AnalyzerDiscriminatorimpl パラメーターを使用して指定できます。

    @Indexed
    @AnalyzerDefs({
        @AnalyzerDef(name = "en",
            tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
            filters = {
                @TokenFilterDef(factory = LowerCaseFilterFactory.class),
                @TokenFilterDef(factory = EnglishPorterFilterFactory.class)
            }),
        @AnalyzerDef(name = "de",
            tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
            filters = {
                @TokenFilterDef(factory = LowerCaseFilterFactory.class),
                @TokenFilterDef(factory = GermanStemFilterFactory.class)
            })
        })
    public class BlogEntry {
    
        @Field
        @AnalyzerDiscriminator(impl = LanguageDiscriminator.class)
        private String language;
    
        @Field
        private String text;
    
        private Set<BlogEntry> references;
    
        // standard getter/setter
    }
  2. Discriminator インターフェースの実装

    Lucene ドキュメントにフィールドを追加するたびに呼び出される getAnalyzerDefinitionName() メソッドを実装します。インデックス化されたエンティティーもインターフェースメソッドに渡されます。

    @AnalyzerDiscriminator がクラスレベルではなく、プロパティーレベルに配置されると value パラメーターが設定されます。この例では、値はこのプロパティーの現在の値を表します。

    public class LanguageDiscriminator implements Discriminator {
        public String getAnalyzerDefinitionName(Object value, Object entity, String field) {
            if (value == null || !(entity instanceof Article)) {
                return null;
            }
            return (String) value;
        }
    }

16.5.8. アナライザーの読み出し

ステミングや音声的近似などを活用するため、ドメインモデルで複数のアナライザーが使用された場合にアナライザーを読み出すことができます。この場合、同じアナライザーを使用してクエリーを構築します。この代わりに、正しいアナライザーを自動的に選択する Lucene ベースの Query API を使用することもできます。詳細は Lucene クエリーの構築 を参照してください。

エンティティーのスコープ指定されたアナライザーは、Lucene プログラム API または Lucene クエリーパーサーのどちらかを使用して読み出すことができます。スコープ指定されたアナライザーは、インデックス化されたフィールドに応じて正しいアナライザーを適用します。個々のフィールドで動作する複数のアナライザーは 1 つのエンティティーで定義できます。スコープ指定されたアナライザーは、これらのアナライザーをコンテキストを認識するアナライザーに統合します。

以下の例では、曲名は 2 つのフィールドでインデックス化されます。

  • 標準のアナライザー: title フィールドで使用されます。
  • ステミングアナライザー: title_stemmed フィールドで使用されます。

クエリーは、検索ファクトリーによって提供されるアナライザーを使用して、目的のフィールドに応じて適切なアナライザーを使用します。

フルテキストクエリーの構築時にスコープ指定されたアナライザーを使用

SearchManager manager = Search.getSearchManager(cache);

org.apache.lucene.queryparser.classic.QueryParser parser = new QueryParser(
        org.apache.lucene.util.Version.LUCENE_5_5_1,
        "title",
        manager.getAnalyzer(Song.class)
);

org.apache.lucene.search.Query luceneQuery =
        parser.parse("title:sky Or title_stemmed:diamond");

// wrap Lucene query in a org.infinispan.query.CacheQuery
CacheQuery cacheQuery = manager.getQuery(luceneQuery, Song.class);

List result = cacheQuery.list();
//return the list of matching objects

注記

@AnalyzerDef によって定義されたアナライザーは、searchManager.getAnalyzer(String) を使用して定義名で取得することもできます。

16.5.9. 使用可能なアナライザー

Apache Solr と Lucene には、デフォルトの CharFilterstokenizers、および filters が複数含まれています。CharFiltertokenizer、および filter ファクトリーの完全リストは http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters を参照してください。以下の表は、CharFilterstokenizers、および filters の一部を示しています。

表16.1 利用可能な CharFilters の例

ファクトリー説明Parameters追加の依存関係

MappingCharFilterFactory

リソースファイルに指定されたマッピングを基に、1 つ以上の文字を置き換えます。

mapping: 以下の形式を使用したマッピングが含まれるリソースファイルを示します。

                    "á" => "a"
                    "ñ" => "n"
                    "ø" => "o"

なし

HTMLStripCharFilterFactory

標準の HTML タグを削除し、テキストは保持します。

なし

なし

表16.2 利用可能なトークナイザーの例

ファクトリー説明Parameters追加の依存関係

StandardTokenizerFactory

Lucene の標準トークナイザーを使用します。

なし

なし

HTMLStripCharFilterFactory

標準の HTML タグを削除してテキストは保持し、StandardTokenizer へ渡します。

なし

solr-core

PatternTokenizerFactory

指定の正規表現パターンでテキストを改行します。

pattern: トークン化に使用する正規表現。

group: トークンに抽出するパターングループを示します。

solr-core

表16.3 利用可能なフィルターの例

ファクトリー説明Parameters追加の依存関係

StandardFilterFactory

頭字語からピリオドを削除し、単語からアポストロフィー (') を削除します。

なし

solr-core

LowerCaseFilterFactory

すべての言葉を小文字にします。

なし

solr-core

StopFilterFactory

ストップワードのリストと一致する言葉 (トークン) を削除します。

words: ストップワードが含まれるリソースファイルを示します。

ignoreCase: ストップワードを比較するときに case を無視する場合は true、無視しない場合は false

solr-core

SnowballPorterFilterFactory

単語を指定言語の語根にします。たとえば、protect、protects、および protection はすべて同じ語根を持ちます。このようなフィルターを使用すると、関連する単語に一致する検索を実行できます。

language: デンマーク語、オランダ語、英語、フィンランド語、フランス語、ドイツ語、イタリア語、ノルウェー語、ポルトガル語、ロシア語、スペイン語、スウェーデン語など。

solr-core

ISOLatin1AccentFilterFactory

フランス語などの言語に使用されるアクセント記号を削除します。

なし

solr-core

PhoneticFilterFactory

音声的に似ているトークンをトークンストリームに挿入します。

encoder: DoubleMetaphone、Metaphone、Soundex、または RefinedSoundex のいずれか。

inject: true を設定するとトークンをストリームに追加し、falseを設定すると既存のトークンを置き換えます。

maxCodeLength: 生成されるコードの最大長を設定します。Metaphone および DoubleMetaphone エンコーディングのみに対してサポートされます。

solr-core および commons-codec

CollationKeyFilterFactory

各トークンを java.text.CollationKey に変換し、CollationKeyIndexableBinaryStringTools でエンコードして索引語 (index term) として保存されるようにします。

customlanguagecountryvariantstrengthdecomposition。詳細は Lucene の CollationKeyFilter javadocs を参照してください。

solr-core および commons-io

16.6. ブリッジ

16.6.1. ブリッジ

エンティティーをマッピングするとき、Lucene はすべてのインデックスフィールドを文字列として表します。@Field アノテーションが付けられたエンティティープロパティーは、すべて文字列に変換され、インデックス化されます。ビルトインブリッジは Lucene ベースの Query API に対してプロパティーを自動的に変換します。ブリッジをカスタマイズすると変換プロセスを制御することができます。

16.6.2. ビルトインブリッジ

Lucene ベースの Query API には、Java プロパティー型とそのフルテキスト表現との間のビルドインブリッジが含まれています。

null
デフォルトでは null 要素はインデックス化されません。 Lucene は null 要素をサポートしませんが、場合によっては null の値を表すカスタムトークンを挿入すると便利なことがあります。詳細は @Field を参照してください。
java.lang.String

文字列は以下としてインデックス化されます。

  • shortShort
  • integerInteger
  • longLong
  • floatFloat
  • doubleDouble
  • BigInteger
  • BigDecimal

数字は文字列の表現に変換されます。Lucene では数字を比較できず、そのままの状態で範囲指定のクエリーに使用できないため、パディングを行う必要があります。

注記

Range クエリーの使用には欠点があるため、代わりに結果クエリーを適切な範囲にフィルターする Filter クエリーを使用することができます。

クエリーモジュールは、カスタム StringBridge の使用をサポートします。Custom Bridges を参照してください。

java.util.Date

日付はグリニッジ標準時 (GMT) で yyyyMMddHHmmssSSS の形式で保存されます。たとえば、アメリカ東部標準時 (EST) の 2006 年 11 月 7 日午後 4 時 3 分 12 秒は 200611072203012 になります。TermRangeQuery を使用する場合、日付は GMT で表示されます。

@DateBridge はインデックスへの保存に適切な精度 (resolution) を定義します (例: @DateBridge(resolution=Resolution.DAY))。日付パターンはこの定義に従って省略されます。

@Indexed
public class Meeting {
    @Field(analyze=Analyze.NO)
    @DateBridge(resolution=Resolution.MINUTE)
    private Date date;

デフォルトの Dateブリッジは ucene の DateTools を使用して String からの変換と String への変換を行います。日付を固定のタイムゾーンで保存するには、カスタムの date ブリッジを実装します。

java.net.URI、java.net.URL
URI および URL は文字列の表現に変換されます。
java.lang.Class
クラスは完全修飾クラス名に変換されます。クラスがリハイドレートされるとスレッドコンテキストクラスローダーが使用されます。

16.6.3. カスタムブリッジ

16.6.3.1. カスタムブリッジ

カスタムブリッジは、ビルトインブリッジやブリッジの文字列表現が、必要なプロパティー型に十分に対応できない場合に使用することができます。

16.6.3.2. FieldBridge

柔軟性を向上するため、ブリッジを FieldBridge として実装できます。FieldBridge インターフェースは、Lucene Document でマップできるプロパティー値を提供します。たとえば、1 つのプロパティーを 2 つのドキュメントフィールドに保存できます。

FieldBridge インターフェースの実装

public class DateSplitBridge implements FieldBridge {
    private final static TimeZone GMT = TimeZone.getTimeZone("GMT");

    public void set(String name,
                    Object value,
                    Document document,
                    LuceneOptions luceneOptions) {
        Date date = (Date) value;
        Calendar cal = GregorianCalendar.getInstance(GMT);
        cal.setTime(date);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DAY_OF_MONTH);

        // set year
        luceneOptions.addFieldToDocument(
            name + ".year",
            String.valueOf(year),
            document);

        // set month and pad it if needed
        luceneOptions.addFieldToDocument(
            name + ".month",
            month < 10 ? "0" : "" + String.valueOf(month),
            document);

        // set day and pad it if needed
        luceneOptions.addFieldToDocument(
            name + ".day",
            day < 10 ? "0" : "" + String.valueOf(day),
            document);
    }
}

//property
@FieldBridge(impl = DateSplitBridge.class)
private Date date;

以下の例では、フィールドは直接 Lucene Document に追加されません。代わりに、追加は LuceneOptions ヘルパーに委譲されます。このヘルパーは、StoreTermVector などの @Field で選択されたオプションを適用したり、選択された @Boost の値を適用したりします。

LuceneOptions を委譲してフィールドを Document に追加することが推奨されますが、LuceneOptions を無視して直接 Document を編集することも可能です。

注記

LuceneOptions は、Lucene API の変更がアプリケーションに影響しないようにし、コードを簡易化します。

16.6.3.3. StringBridge

org.infinispan.query.bridge.StringBridge インターフェースを使用して、想定される Object の実装で Lucene ベースの Query API を String ブリッジまたは StringBridge に提供します。すべての実装は同時に使用されるため、スレッドセーフである必要があります。

カスタム StringBridge 実装

/**
 * Padding Integer bridge.
 * All numbers will be padded with 0 to match 5 digits
 *
 * @author Emmanuel Bernard
 */
public class PaddedIntegerBridge implements StringBridge {

    private int PADDING = 5;

    public String objectToString(Object object) {
        String rawInteger = ((Integer) object).toString();
        if (rawInteger.length() > PADDING)
            throw new IllegalArgumentException("Try to pad on a number too big");
        StringBuilder paddedInteger = new StringBuilder();
        for (int padIndex = rawInteger.length() ; padIndex < PADDING ; padIndex++) {
            paddedInteger.append('0');
        }
        return paddedInteger.append(rawInteger).toString();
    }
}

@FieldBridge アノテーションを使用すると、この例のプロパティーまたはフィールドはブリッジを使用できます。

@FieldBridge(impl = PaddedIntegerBridge.class)
private Integer length;

16.6.3.4. 双方向ブリッジ

TwoWayStringBridgeStringBridge の拡張バージョンで、ブリッジ実装が ID プロパティーで使用される場合に使用できます。Lucene ベースの Query API は識別子の文字列表現を読み取り、オブジェクトの生成に使用します。 @FieldBridge アノテーションは同様に使用されます。

ID プロパティーの TwoWayStringBridge の実装

public class PaddedIntegerBridge implements TwoWayStringBridge, ParameterizedBridge {

    public static String PADDING_PROPERTY = "padding";
    private int padding = 5; //default

    public void setParameterValues(Map parameters) {
        Object padding = parameters.get(PADDING_PROPERTY);
        if (padding != null) this.padding = (Integer) padding;
    }

    public String objectToString(Object object) {
        String rawInteger = ((Integer) object).toString();
        if (rawInteger.length() > padding)
            throw new IllegalArgumentException("Try to pad on a number too big");
        StringBuilder paddedInteger = new StringBuilder();
        for (int padIndex = rawInteger.length(); padIndex < padding; padIndex++) {
            paddedInteger.append('0');
        }
        return paddedInteger.append(rawInteger).toString();
    }

    public Object stringToObject(String stringValue) {
        return new Integer(stringValue);
    }
}


@FieldBridge(impl = PaddedIntegerBridge.class,
             params = @Parameter(name = "padding", value = "10"))
private Integer id;

重要

object = stringToObject(objectToString(object))) のように、双方向処理はべき等である必要があります。

16.6.3.5. パラメーター化されたブリッジ

ParameterizedBridge インターフェースはパラメーターをブリッジ実装に渡し、柔軟性を向上します。ParameterizedBridgeStringBridgeTwoWayStringBridge、および FieldBridge 実装による実行が可能です。すべての実装がスレッドセーフである必要があります。

以下の例は、ParameterizedBridge インターフェースを実装し、パラメーターは @FieldBridge アノテーションによって渡されます。

ParameterizedBridge インターフェースの設定

public class PaddedIntegerBridge implements StringBridge, ParameterizedBridge {

    public static String PADDING_PROPERTY = "padding";
    private int padding = 5; //default

    public void setParameterValues(Map <String,String> parameters) {
        String padding = parameters.get(PADDING_PROPERTY);
        if (padding != null) this.padding = Integer.parseInt(padding);
    }

    public String objectToString(Object object) {
        String rawInteger = ((Integer) object).toString();
        if (rawInteger.length() > padding)
            throw new IllegalArgumentException("Try to pad on a number too big");
        StringBuilder paddedInteger = new StringBuilder();
        for (int padIndex = rawInteger.length() ; padIndex < padding ; padIndex++) {
            paddedInteger.append('0');
        }
        return paddedInteger.append(rawInteger).toString();
    }
}

//property
@FieldBridge(impl = PaddedIntegerBridge.class,
             params = @Parameter(name = "padding", value = "10")
            )
private Integer length;

16.6.3.6. 型対応ブリッジ

AppliedOnTypeAwareBridge を実装するすべてのブリッジは、適用またはインジェクトされるブリッジの型を取得します。

  • フィールド/ゲッターレベルのブリッジに対するプロパティーの戻り値の型。
  • クラスレベルのブリッジに対するクラス型。

インジェクトされた型に特有のスレッドセーフ要件はありません。

16.6.3.7. ClassBridge

@ClassBridge アノテーションを使用すると、1 つのエンティティーの 1 つ以上のプロパティーを Lucene インデックスに固有の方法で組み合わせたりインデックス化することができます。@ClassBridge はクラスレベルで定義でき、termVector 属性をサポートします。

以下の例では、カスタムの FieldBridge 実装はエンティティーインスタンスを値パラメーターとして受け取ります。特定の CatFieldsClassBridge が department インスタンスに適用されます。FieldBridge はブランチとネットワークの両方を連結し、その連結をインデックス化します。

ClassBridge の実装

@Indexed
@ClassBridge(name = "branchnetwork",
             store = Store.YES,
             impl = CatFieldsClassBridge.class,
             params = @Parameter(name = "sepChar", value = ""))
public class Department {
    private int id;
    private String network;
    private String branchHead;
    private String branch;
    private Integer maxEmployees;
}

public class CatFieldsClassBridge implements FieldBridge, ParameterizedBridge {
    private String sepChar;

    public void setParameterValues(Map parameters) {
        this.sepChar = (String) parameters.get("sepChar");
    }

    public void set(String name,
                    Object value,
                    Document document,
                    LuceneOptions luceneOptions) {

        Department dep = (Department) value;
        String fieldValue1 = dep.getBranch();
        if (fieldValue1 == null) {
            fieldValue1 = "";
        }
        String fieldValue2 = dep.getNetwork();
        if (fieldValue2 == null) {
            fieldValue2 = "";
        }
        String fieldValue = fieldValue1 + sepChar + fieldValue2;
        Field field = new Field(name, fieldValue, luceneOptions.getStore(),
            luceneOptions.getIndex(), luceneOptions.getTermVector());
        field.setBoost(luceneOptions.getBoost());
        document.add(field);
   }
}