14.2. インデックス構造へのエンティティーのマッピング
14.2.1. エンティティーのマッピング
エンティティーのインデックス化に必要なすべてのメタデータ情報はアノテーションで記述されるため、XML マッピングファイルは必要ありません。基本的な Hibernate 設定には Hibernate マッピングファイルも使用できますが、Hibernate Search 固有の設定はアノテーションで表現する必要があります。
14.2.1.1. 基本的なマッピング
エンティティーのマッピングに最も一般的に使用されるアノテーションから始めましょう。
Lucene ベースの Query API は、以下の共通アノテーションを使用してエンティティーをマッピングします。
- @Indexed
- @Field
- @NumericField
- @Id
14.2.1.1.1. @Indexed
ほとんどの場合、永続クラスをインデックス化可能として宣言する必要があります。これは、クラスに
@Indexed
アノテーションを付けることで行われます (@Indexed
アノテーションが付いていないすべてのエンティティーはインデックスプロセスによって無視されます)。
例14.8 @Indexed
を使用してクラスをインデックス可能にする
@Entity
@Indexed
public class Essay {
...
}
オプションで @Indexed アノテーションの
index
属性を指定して、インデックスのデフォルト名を変更できます。
14.2.1.1.2. @Field
エンティティーのプロパティー(または属性) ごとに、インデックス化方法を記述する機能があります。デフォルト (アノテーションなし) は、インデックスプロセスによってプロパティーが無視されることを意味します。
@Field
はプロパティーをインデックスとして宣言し、以下の属性のいずれかを設定してインデックスプロセスの複数の側面を設定できます。
name
: プロパティーがどの名前下で、Lucene Document に保存されるべきかを説明します。デフォルト値はプロパティー名です (続く JavaBeans 規則)。store
: プロパティーが Lucene インデックスに保存されているかどうかを示します。Store.YES
の値 (インデックスに多くの領域が必要ですが projection を許可 「プロジェクション」 を参照) を保存するか、これを圧縮方式Store.COMPRESS
で保存、あるいは、Store.NO
を回避することができます (デフォルト値)。プロパティーが保存されると、Lucene ドキュメントから元の値を取得できます。これは、要素がインデックス化されるかどうかに関連しません。index
: プロパティーがインデックス化されるかどうかを示します。異なる値は、Index.NO
(インデックス付けなし、つまりクエリーで見つけることができない)、Index.YES
(要素にインデックスが付けられて検索可能) です。デフォルト値はIndex.YES
です。Index.NO
は、プロパティーの検索が不可能であるものの、利用できる必要がある場合に便利です。注記Index.NO
をAnalyze.YES
またはNorms.YES
と組み合わせると有用なわけではありません。これは、analyze
とnorms
と解析のプロパティーのインデックス作成が必要ないためです。analyze
: プロパティーが分析されたかどうか (Analyze.YES
) または (Analyze.NO
) を判断します。デフォルト値はAnalyze.YES
です。注記プロパティーを分析するかどうかは、要素をそのまま0検索する場合と、含まれる単語で検索するかによって異なります。テキストフィールドを分析することは理にかなっていますが、日付フィールドは分析しません。注記ソートに使用されるフィールドは、分析できません。norms
: インデックス時間の改善情報を保存する必要があるかどうか (Norms.YES
) または (Norms.NO
) を示します。これを保存しないと、大量のメモリーを節約できますが、インデックスの時間が改善する情報は提供されません。デフォルト値はNorms.YES
です。termVector
: 用語と周波数のペア (term-frequency) のコレクションについて説明しています。この属性により、インデックス作成中にドキュメント内にベクターを保存することができます。デフォルト値はTermVector.NO
です。この属性の異なる値は次のとおりです。値 定義 TermVector.YES 各ドキュメントのTerm Vectors を保存します。これにより、同期されたアレイが作成され、これらはドキュメント用語が含まれ、他は用語の周波数が含まれます。 TermVector.NO Term Vector は保存しないでください。 TermVector.WITH_OFFSETS Term Verctor およびトークンオフセット情報を保存します。これは TermVector.YES と同様で、用語の開始および終了オフセット位置情報が含まれます。 TermVector.WITH_POSITIONS Term Verctor およびトークン位置情報を保存します。これは TermVector.YES と同じですが、ドキュメント内の各用語の特徴も含まれます。 TermVector.WITH_POSITION_OFFSETS Term Vector 、トークンの位置、およびオフセット情報を格納します。これは、YES、WITH_OFFSETS、および WITH_POSITIONS の組み合わせです。 indexNullAs
: デフォルトの null 値ごとに無視され、インデックスは作成されません。ただし、indexNullAs
を使用すると、null
値のトークンとして挿入される文字列を指定できます。デフォルトでは、この値はに設定されていますField.DO_NOT_INDEX_NULL
そのことを示すnull
値にインデックスを付けないでください。この値を次のように設定できますField.DEFAULT_NULL_TOKEN
デフォルトであることを示すnull
トークンを使用する必要があります。このデフォルトのnull
トークンは、hibernate.search.default_null_token
を使用して設定に指定できます。このプロパティーが設定されておらず、指定した場合Field.DEFAULT_NULL_TOKEN
文字列 _null_ がデフォルトとして使用されます。注記indexNullAs
パラメーターを使用する場合は、検索クエリーで同じトークンを使用してnull
値を検索することが重要です。また、この機能は、非分析フィールド (
) でのみ使用することが推奨されます。analyze=
Analyze.NO警告カスタムFieldBridge
またはTwoWayFieldBridge
を実装する場合、null 値のインデックス作成を処理するのは開発者の責任です (の JavaDocs を参照してください。LuceneOptions.indexNullAs()
)。
14.2.1.1.3. @NumericField
@Field
には @NumericField
というコンパニオンアノテーションがあり、@Field
または @DocumentId
と同じスコープで指定できます。このプロパティーは、Integer、Long、Float、および LastName プロパティーに指定できます。インデックスの作成時に、値は Trie 構造を使用してインデックス化されます。プロパティーを数字フィールドとしてインデックス化すると、標準的な @Field
プロパティーに対して同じクエリーを実行するよりも、効率的な範囲クエリーとソートが可能になります。@NumericField
アノテーションは以下のパラメーターを受け入れます。
値 | 定義 |
---|---|
forField | (オプション) 数値としてインデックス化される関連 @Field の名前を指定します。@Field 宣言を超えるプロパティーが含まれる場合にのみ必須となります。 |
precisionStep | (オプション) インデックスに Trie 構造を格納する方法を変更します。precisionStes を小さくすると、ディスク領域の使用率が高くなり、範囲やソートのクエリーが速くなります。値が大きいほど使用領域が少なくなり、クエリーのパフォーマンスは通常の @Fields の範囲のクエリーに近づくことになります。デフォルト値は 4 です。 |
NumericField
は、Double
、Long
、Integer
、Float
のみをサポートしています。他の数値タイプには Lucene で同様の機能を活用できないため、残りのタイプはデフォルトまたはカスタムの TwoWayFieldBridge
で文字列エンコーディングを使用する必要があります。
タイプ変換中に概算を処理できると仮定した場合は、カスタムの
NumericFieldBridge
を使用することができます。
例14.9 カスタム 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 ); } }
14.2.1.1.4. @Id
最後に、エンティティーの
id
(identifier) プロパティーは、特定のエンティティーのインデックスを一意に保つために Hibernate Search で使用される特別なプロパティーです。設計上、id
は保存する必要があり、トークン化しないでください。プロパティーをインデックス識別子としてマークするには、@DocumentId
アノテーションを使用します。JPA を使用し、@Id
を指定した場合は、@DocumentId
を省略できます。選択したエンティティー ID は、ドキュメント識別子として使用されます。
例14.10 インデックス付きプロパティーの指定
@Entity @Indexed public class Essay { ... @Id @DocumentId public Long getId() { return id; } @Field(name="Abstract", store=Store.YES) public String getSummary() { return summary; } @Lob @Field public String getText() { return text; } @Field @NumericField( precisionStep = 6) public float getGrade() { return grade; } }
例14.10「インデックス付きプロパティーの指定」
id
、Abstract
、text
、grade
の 4 つのフィールドでインデックスを定義します。デフォルトでは、JavaBean 仕様にしたがってフィールド名は大文字では表示されないことに注意してください。Grade
フィールドは、デフォルトよりも若干精度の高いステップで数字としてアノテーションが付けられます。
14.2.1.2. 複数回のプロパティーのマッピング
若干異なるインデックスストラテジーで、インデックスごとにプロパティーを複数回マップする必要がある場合があります。たとえば、フィールド別にクエリーを並び替えるには、フィールドを分析解除する必要があります。このプロパティーの単語で検索し、これをソートするには、分析後と未分析のときにインデックス付けする必要があります。これは、@Fields を使用することで可能です。
例14.11 @Fields を使用してプロパティーを複数回マップする
@Entity @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
、非トークン化方式の summary_forSort
で行われます。
14.2.1.3. 埋め込みおよび関連オブジェクト
関連オブジェクトおよび組み込みオブジェクトは、ルートエンティティーインデックスの一部としてインデックス化できます。これは、関連するオブジェクトのプロパティーに基づいて特定のエンティティーを検索する場合に役に立ちます。例14.12「インデックスアソシエーション」 関連する都市が Atlanta である場所を返すことを目的としています (Lucene クエリーパーサー言語で、
address.city:Atlanta
に変換されます)。場所フィールドは、Place
インデックスでインデックス化されます。Placement
インデックスドキュメントには、クエリー可能な address.id
、address.street
、address.city
フィールドも含まれます。
例14.12 インデックスアソシエーション
@Entity @Indexed public class Place { @Id @GeneratedValue @DocumentId private Long id; @Field private String name; @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } ) @IndexedEmbedded private Address address; .... } @Entity public class Address { @Id @GeneratedValue private Long id; @Field private String street; @Field private String city; @ContainedIn @OneToMany(mappedBy="address") private Set<Place> places; ... }
@IndexedEmbedded
技術を使用すると、データは Lucene インデックスで非正規化されるため、Hibernate Search は Place
オブジェクトのすべての変更と、インデックスを最新の状態に保つため Address
オブジェクトの変更を認識する必要があります。Place
Lucene ドキュメントが Address
の変更時に更新されるようにするには、双方向関係の反対側に @ContainedIn
のマークを付けます。
注記
@ContainedIn
はエンティティーを参照する関連付けや、組み込み (コレクション) オブジェクトを参照する関連付けで役立ちます。
この例を展開するために、以下の例では @IndexedEmbedded のネスト化を示しています。
例14.13 @IndexedEmbedded
および @ContainedIn
のネスト化された使用方法
@Entity @Indexed public class Place { @Id @GeneratedValue @DocumentId private Long id; @Field private String name; @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } ) @IndexedEmbedded private Address address; .... } @Entity public class Address { @Id @GeneratedValue private Long id; @Field private String street; @Field private String city; @IndexedEmbedded(depth = 1, prefix = "ownedBy_") private Owner ownedBy; @ContainedIn @OneToMany(mappedBy="address") private Set<Place> places; ... } @Embeddable public class Owner { @Field private String name; ... }
@*ToMany, @*ToOne
および @Embedded
属性は、@IndexedEmbedded
アノテーションを付けることができます。その後、関連クラスの属性が主なエンティティーインデックスに追加されます。例14.13「@IndexedEmbedded
および @ContainedIn
のネスト化された使用方法」 インデックスには以下のフィールドが含まれます。
- id
- name
- address.street
- address.city
- address.ownedBy_name
デフォルトの接頭辞は
propertyName.
で、従来のオブジェクトナビゲーション規則に従います。ownedBy
プロパティーに示されるように、prefix
属性を使用して上書きできます。
注記
接頭辞を空の文字列に設定することはできません。
depth
プロパティーは、オブジェクトグラフにクラス (インスタンスではない) の cyclic 依存関係が含まれるときに必要になります。たとえば、Owner
が Place
をポイントする場合です。Hibernate Search は、予想される深さに達すると (またはオブジェクトグラフの境界に到達する)、インデックス化された組み込み属性を含まなくなります。自己参照を持つクラスは、cyclic 依存関係の例です。この例では、depth
が 1 に設定されているため、Owner の @IndexedEmbedded
属性は無視されます。
オブジェクト関連付けに
@IndexedEmbedded
を使用すると、以下のようなクエリーを表現できます (Lucene のクエリー構文を使用)。
- 名前に JBoss が含まれ、住所の都市がアトランタである場所を返します。Lucene クエリーでは、以下のようになります。
+name:jboss +address.city:atlanta
- 名前に JBoss が含まれ、所有者の名前に Joe が含まれる場所を返します。Lucene クエリーでは、以下のようになります。
+name:jboss +address.ownedBy_name:joe
この動作は、より効率的な方法 (データの重複が犠牲となる) でのリレーショナルジョイン操作の操作に似ています。初期状態の Lucene インデックスには関連付けの概念がないため、join 操作が存在しないことに注意してください。これは、完全なテキストインデックスの速度と機能が充実した状態で、リレーショナルデータベースを維持するのに役立ちます。
注記
関連付けられたオブジェクトは、
@Indexed
にすることができます (ただし、必須ではありません)。
@IndexedEmbedded がエンティティーを参照する場合、関連付けには指向性が必要で、反対側にはアノテーション
@ContainedIn
を付ける必要があります (前述の例を参照)。これがない場合、Hibernate Search は関連エンティティーの更新時にルートインデックスを更新することはできません (この例では、関連付けられた Address
インスタンスの更新時に Place
インデックスドキュメントを更新する必要があります)。
@IndexedEmbedded
アノテーションが付けられたオブジェクトタイプは、Hibernate および Hibernate Search によってターゲットに設定されたオブジェクトタイプではない場合があります。これは、インターフェースが実装の代わりに使用される場合にとくに当てはまります。このため、Hibernate Search の対象となるオブジェクトタイプを、targetElement
パラメーター。
例14.14 @IndexedEmbedded
の targetElement
プロパティーの使用
@Entity
@Indexed
public class Address {
@Id
@GeneratedValue
@DocumentId
private Long id;
@Field
private String street;
@IndexedEmbedded(depth = 1, prefix = "ownedBy_", targetElement = Owner.class)
@Target(Owner.class)
private Person ownedBy;
...
}
@Embeddable
public class Owner implements Person { ... }
14.2.1.4. 特定のパスへのオブジェクト埋め込みの制限
@IndexedEmbedded
アノテーションは属性も提供しますincludePaths
の代わりに使用できますdepth
、またはそれと組み合わせる。
depth
のみを使用すると、埋め込み型のインデックス設定されたフィールドはすべて、同じデプスで再帰的に追加されます。これにより、他のフィールドもすべて追加せずに特定のパスのみを選択することが困難になります。これは必須ではありません。
不要な読み込みおよびインデックスエンティティーを回避するには、必要なパスを正確に指定することができます。通常のアプリケーションでは、パスごとに異なるデプスが必要な場合や、例14.15「
@IndexedEmbedded
の includePaths
プロパティーの使用」 のようにパスを明示的に指定する必要がある場合があります。
例14.15 @IndexedEmbedded
の includePaths
プロパティーの使用
@Entity
@Indexed
public class Person {
@Id
public int getId() {
return id;
}
@Field
public String getName() {
return name;
}
@Field
public String getSurname() {
return surname;
}
@OneToMany
@IndexedEmbedded(includePaths = { "name" })
public Set<Person> getParents() {
return parents;
}
@ContainedIn
@ManyToOne
public Human getChild() {
return child;
}
...//other fields omitted
のようにマッピングを使用する例14.15「
@IndexedEmbedded
の includePaths
プロパティーの使用」、名前
や名前、および/または 親
の 名前
で 人物
を検索できます。親の surname
をインデックス化しないため、親の surname を検索することはできません。ただし、インデックス作成を迅速化し、スペースを節約して、全体的なパフォーマンスを向上させることができます。
@IndexedEmbedded
includePaths
制限された値を指定して通常インデックスを作成 するものに加えて、指定されたパスが含まれますdepth
。使用する場合includePaths
、そして去るdepth
未定義、動作は設定と同等ですdepth
= 0
: 含まれているパスのみにインデックスが付けられます。
例14.16 @IndexedEmbedded
の includePaths
プロパティーの使用
@Entity
@Indexed
public class Human {
@Id
public int getId() {
return id;
}
@Field
public String getName() {
return name;
}
@Field
public String getSurname() {
return surname;
}
@OneToMany
@IndexedEmbedded(depth = 2, includePaths = { "parents.parents.name" })
public Set<Human> getParents() {
return parents;
}
@ContainedIn
@ManyToOne
public Human getChild() {
return child;
}
...//other fields omitted
の例14.16「
@IndexedEmbedded
の includePaths
プロパティーの使用」、すべての人間の名前と名前の属性にインデックスが付けられます。また、depth
属性が原因で、親の名前と surname (姓) も再帰的に 2 行目にインデックス化されます。人が直接、自身の親、または親の名前で検索することができます。第 2 レベル以外では、姓 (surname) ではなく、もう 1 レベル (名前のみ) をインデックス化します。
これにより、インデックスに以下のフィールドが生成されます。
id
- プライマリーキーとして_hibernate_class
- エンティティータイプを保存name
- 直接フィールドとしてsurname
- 直接フィールドとしてparents.name
- デプス 1 の埋め込みフィールドとしてparents.surname
- デプス 1 の埋め込みフィールドとしてparents.parents.name
: デプス 2 の埋め込みフィールドとしてparents.parents.surname
- デプス 2 の埋め込みフィールドとして親.parents.parents.name-
によって指定された追加のパスとしてincludePaths
。最初のparents.
はフィールド名から推測され、残りのパスはincludePaths
の属性です。
インデックス化されたパスを明示的に制御することは、必要なクエリーを最初に定義してアプリケーションを設計する場合に容易になる可能性があります。この時点では、どのフィールドが必要かを正確に把握している可能性もあります。その他のどのフィールドがユースケースを実装する必要はありません。