第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、トークン位置、およびオフセット情報を保存します。これはYES
、WITH_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
は、Double
、Long
、Integer
、および 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
プロパティーが使用されます。たとえば、Owner
が Place
を示す場合がこれに該当します。予期された深さまたはオブジェクトグラフの境界に達した後、クエリーモジュールは属性の追加を停止します。自己参照クラスは循環依存関係の例の 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 が目的とするオブジェクト型ではない場合に使用されます。これは、実装の代わりにインターフェースが使用されると発生します。
@IndexedEmbedded
の targetElement
プロパティーの使用
@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 で、これらはそれぞれ PropertyAnalyzer
と FieldAnalyzer
によってインデックス化されます。
@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
はこれらのコンポーネントを複数のタスクに分類し、以下の手順を使用して各コンポーネントの再使用やコンポーネントの柔軟な構築を実現します。
アナライザーの処理
-
CharFilters
は文字入力を処理します。 -
Tokenizer
は文字入力をトークンに変換します。 -
トークンは
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 フレームワーク
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") }) },
Tokenizer の定義
Tokenizer
はStandardTokenizerFactory.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)
フィルターのリスト
ファクトリーでフィルターのリストを定義します。この例では、専用の単語プロパティーファイルを読み取って
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. アナライザーリソースのロード
Tokenizers
、TokenFilters
、および CharFilters
は StopFilterFactory.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 の設定
動的なアナライザーの事前定義
@AnalyzerDiscriminator
には、動的に使用されるすべてのアナライザーを@AnalyzerDef
を使用して事前定義する必要があります。動的にアナライザーを選択するために、@AnalyzerDiscriminator
アノテーションをクラスまたはエンティティーの特定のプロパティーに配置できます。Discriminator
インターフェースの実装は、@AnalyzerDiscriminator
impl
パラメーターを使用して指定できます。@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 }
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 には、デフォルトの CharFilters
、tokenizers
、および filters
が複数含まれています。CharFilter
、tokenizer
、および filter
ファクトリーの完全リストは http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters を参照してください。以下の表は、CharFilters
、tokenizers
、および filters
の一部を示しています。
表16.1 利用可能な CharFilters の例
ファクトリー | 説明 | Parameters | 追加の依存関係 |
---|---|---|---|
|
リソースファイルに指定されたマッピングを基に、1 つ以上の文字を置き換えます。 |
"á" => "a" "ñ" => "n" "ø" => "o" |
なし |
|
標準の HTML タグを削除し、テキストは保持します。 |
なし |
なし |
表16.2 利用可能なトークナイザーの例
ファクトリー | 説明 | Parameters | 追加の依存関係 |
---|---|---|---|
|
Lucene の標準トークナイザーを使用します。 |
なし |
なし |
|
標準の HTML タグを削除してテキストは保持し、StandardTokenizer へ渡します。 |
なし |
|
|
指定の正規表現パターンでテキストを改行します。 |
group: トークンに抽出するパターングループを示します。 |
|
表16.3 利用可能なフィルターの例
ファクトリー | 説明 | Parameters | 追加の依存関係 |
---|---|---|---|
|
頭字語からピリオドを削除し、単語からアポストロフィー (') を削除します。 |
なし |
|
|
すべての言葉を小文字にします。 |
なし |
|
|
ストップワードのリストと一致する言葉 (トークン) を削除します。 |
ignoreCase: ストップワードを比較するときに |
|
|
単語を指定言語の語根にします。たとえば、protect、protects、および protection はすべて同じ語根を持ちます。このようなフィルターを使用すると、関連する単語に一致する検索を実行できます。 |
|
|
|
フランス語などの言語に使用されるアクセント記号を削除します。 |
なし |
|
|
音声的に似ているトークンをトークンストリームに挿入します。 |
inject:
|
|
|
各トークンを |
|
|
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
文字列は以下としてインデックス化されます。
-
short
、Short
-
integer
、Integer
-
long
、Long
-
float
、Float
-
double
、Double
-
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
ヘルパーに委譲されます。このヘルパーは、Store
や TermVector
などの @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. 双方向ブリッジ
TwoWayStringBridge
は StringBridge
の拡張バージョンで、ブリッジ実装が 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
インターフェースはパラメーターをブリッジ実装に渡し、柔軟性を向上します。ParameterizedBridge
は StringBridge
、TwoWayStringBridge
、および 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); } }