Red Hat Training

A Red Hat training course is available for Red Hat JBoss Enterprise Application Platform

14.3.3. フィルター

Apache Lucene には、カスタムフィルタープロセスに応じてクエリーの結果をフィルタリングできる強力な機能があります。これは、特にフィルターをキャッシュして再利用できるため、追加のデータ制限を適用する非常に強力な方法です。ユースケースには以下が含まれます。
  • セキュリティー
  • 一時的なデータ (例: 先月のデータのみ表示)
  • 予測フィルター (例: 検索は所定カテゴリーに限定される)
Hibernate Search は、透過的にキャッシュされるパラメーター可能な名前付きフィルターの概念を導入することで、この概念をさらにプッシュします。Hibernate Core フィルターの概念を熟知しているユーザーにとって、API は非常に似ています。

例14.49 クエリーのフルテキストフィルターの有効化

fullTextQuery = s.createFullTextQuery( query, Driver.class );
fullTextQuery.enableFullTextFilter("bestDriver");
fullTextQuery.enableFullTextFilter("security").setParameter( "login", "andre" );
fullTextQuery.list(); //returns only best drivers where andre has credentials
この例では、クエリーの上に複数のフィルターを有効化しています。フィルターはいくつでも有効 (または無効) にできます。
宣言フィルターは @FullTextFilterDef アノテーションを使用して実行されます。このアノテーションは、フィルターが後に適用されるクエリーに関係なく、@Indexed エンティティーに設定できます。これは、フィルター定義がグローバルであり、名前が一意である必要があることを意味します。同じ名前を持つ @FullTextFilterDef アノテーションが定義される場合は、SearchException が発生します。名前付きの各フィルターは、実際のフィルター実装を指定する必要があります。

例14.50 フィルターの定義と実装


@FullTextFilterDefs( {
    @FullTextFilterDef(name = "bestDriver", impl = BestDriversFilter.class), 
    @FullTextFilterDef(name = "security", impl = SecurityFilterFactory.class) 
})
public class Driver { ... }
public class BestDriversFilter extends org.apache.lucene.search.Filter {

    public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
        OpenBitSet bitSet = new OpenBitSet( reader.maxDoc() );
        TermDocs termDocs = reader.termDocs( new Term( "score", "5" ) );
        while ( termDocs.next() ) {
            bitSet.set( termDocs.doc() );
        }
        return bitSet;
    }
}
BestDriversFilter は簡単な Lucene フィルターの例です。これにより、スコアが 5 のドライバーに設定された結果が減少します。この例では、指定したフィルターは org.apache.lucene.search.Filter を直接実装し、no-arg コンストラクターが含まれます。
フィルターの作成に追加のステップが必要な場合や、使用するフィルターに no-arg コンストラクターがない場合は、ファクトリーパターンを使用できます。

例14.51 ファクトリーパターンを使用してフィルターを作成する


@FullTextFilterDef(name = "bestDriver", impl = BestDriversFilterFactory.class)
public class Driver { ... }

public class BestDriversFilterFactory {

    @Factory
    public Filter getFilter() {
        //some additional steps to cache the filter results per IndexReader
        Filter bestDriversFilter = new BestDriversFilter();
        return new CachingWrapperFilter(bestDriversFilter);
    }
}
Hibernate Search は @Factory アノテーションが付けられたメソッドを検索し、そのメソッドを使用してフィルターインスタンスを作成します。ファクトリーには no-arg コンストラクターが必要です。
名前付きフィルターを使用すると、パラメーターをフィルターに渡すことができます。たとえば、セキュリティーフィルターを適用すると、適用するセキュリティーレベルが分かります。

例14.52 定義されたフィルターへのパラメーターの受け渡し

fullTextQuery = s.createFullTextQuery( query, Driver.class );
fullTextQuery.enableFullTextFilter("security").setParameter( "level", 5 );
各パラメーター名は、フィルターや、ターゲットとなる名前付きフィルター定義のフィルターまたはフィルターファクトリーのいずれかに関連するセッターを持つ必要があります。

例14.53 フィルター実装におけるパラメーターの使用

public class SecurityFilterFactory {
    private Integer level;

    /**
     * injected parameter
     */
    public void setLevel(Integer level) {
        this.level = level;
    }

    @Key public FilterKey getKey() {
        StandardFilterKey key = new StandardFilterKey();
        key.addParameter( level );
        return key;
    }

    @Factory
    public Filter getFilter() {
        Query query = new TermQuery( new Term("level", level.toString() ) );
        return new CachingWrapperFilter( new QueryWrapperFilter(query) );
    }
}
@Key アノテーションが付けられたメソッドは FilterKey オブジェクトを返すことに注意してください。返されたオブジェクトには特別なコントラクトを持ちます。キーオブジェクトは equals() / hashCode() を実装し、指定される Filter タイプが同一で、パラメーターのセットが同じである場合にのみ、両方の鍵が同じになるようにする必要があります。つまり、鍵の生成元となるフィルターが交換可能な場合、あるいは交換可能である場合にのみ、両方のフィルター鍵が同等になります。キーオブジェクトは、キャッシュメカニズムのキーとして使用されます。
@Key メソッドは以下の場合にのみ必要です。
  • フィルターキャッシングシステムが有効になっている (デフォルトでは有効)。
  • フィルターにはパラメーターがあります。
多くの場合、StandardFilterKey 実装を使用すれば十分です。それは委任しますequals()/hashCode()各パラメーター equals および hashcode メソッドへの実装。
定義されたフィルターがデフォルトのキャッシュされ、キャッシュは、必要に応じてハード参照とソフト参照の組み合わせを使用してメモリーの破損を可能にします。ハード参照キャッシュは、最近使用されたフィルターを追跡し、必要に応じて SoftReferences に最も使用されるフィルターを変換します。ハード参照キャッシュの制限に達すると、追加のフィルターが SoftReferences としてキャッシュされます。ハード参照キャッシュのサイズを調整するには、hibernate.search.filter.cache_strategy.size (デフォルトは 128 に設定) を使用します。フィルターキャッシングの高度な使用のために、独自の FilterCachingStrategy を実装します。classname は hibernate.search.filter.cache_strategy によって定義されます。
このフィルターキャッシュメカニズムは、実際のフィルター結果をキャッシュするのと混同しないようにしてください。Lucene では、CachingWrapperFilter を中心に IndexReader を使用してフィルターをラッピングすることが一般的です。ラッパーは、から返された DocIdSet をキャッシュしますgetDocIdSet(IndexReader reader)高価な再計算を回避する方法。リーダーは、開いた時点のインデックスの状態を効果的に表示するため、計算した DocIdSet が同じ IndexReader インスタンスに対してのみキャッシュ可能であることを示すことが重要です。ドキュメントリストは、開いている IndexReader 内では変更できません。ただし、別の、新しい IndexReader インスタンスの場合は、(別のインデックスから、または単にインデックスが変更されたため) 異なる Document のセットで動作する可能性があるため、キャッシュされた DocIdSet を再計算する必要があります。
また、Hibernate Search はキャッシングのこの側面にも役立ちます。@FullTextFilterDefcache フラグは、デフォルトでは FilterCacheModeType.INSTANCE_AND_DOCIDSETRESULTS に設定されています。これにより、フィルターインスタンスを自動的にキャッシュし、CachingWrapperFilter の Hibernate 固有の実装に指定されたフィルターをラップします。このクラスの Lucene のバージョンとは対照的に、SoftReference はハード参照数とともに使用されます (フィルターキャッシュについて参照)。ハード参照数は、hibernate.search.filter.cache_docidresults.size (デフォルトは 5 に設定) を使用して調整できます。ラッピング動作は、the@FullTextFilterDef.cache パラメーターを使用して制御できます。このパラメーターには、以下の異なる値があります。
定義
FilterCacheModeType.NONE フィルターインスタンスがなく、Hibernate Search によって結果がキャッシュされません。フィルター呼び出しごとに、新しいフィルターインスタンスが作成されます。この設定は、データセットまたはメモリーが制限される環境を迅速に変更する際に役に立つことがあります。
FilterCacheModeType.INSTANCE_ONLY フィルターインスタンスはキャッシュされ、同時実行全体で再利用されますFilter.getDocIdSet()呼び出します。DocIdSet の結果はキャッシュされません。この設定は、フィルターが固有のキャッシングメカニズムを使用するか、アプリケーション固有のイベントにより DocIdSet をキャッシュする必要がない場合に動的にフィルターの結果が変更される場合に役立ちます。
FilterCacheModeType.INSTANCE_AND_DOCIDSETRESULTS フィルターインスタンスと DocIdSet 両方の結果がキャッシュされます。これはデフォルト値です。
最後になりましたが、フィルターをキャッシュする必要があるのはなぜですか?フィルターキャッシングが優れている領域は 2 つあります。
フィルターは以下の状況でキャッシュする必要があります。
  • システムはターゲットエンティティーインデックスを頻繁に更新しません (つまり、IndexReader は頻繁に再利用されます)。
  • Filter の DocIdSet の計算には、クエリーの実行にかかった時間と比較して負荷がかかります。

14.3.3.1. シャード化された環境でのフィルターの使用

シャード化された環境では、利用可能なシャードのサブセットでクエリーを実行することができます。これを行う方法は 2 つあります。

手順14.1 インデックスシャードのサブセットのクエリー

  1. フィルター設定に応じて IndexManager のサブセットを選択するシャード化ストラテジーを作成します。
  2. クエリー時にフィルターをアクティブにします。

例14.54 インデックスシャードのサブセットのクエリー

この例では、customer フィルターがアクティブな場合は、クエリーが特定の顧客シャードに対して実行されます。
public class CustomerShardingStrategy implements IndexShardingStrategy {
	
	 // stored IndexManagers in a array indexed by customerID
	 private IndexManager[] indexManagers;
	 
	 public void initialize(Properties properties, IndexManager[] indexManagers) {
	   this.indexManagers = indexManagers;
	 }
	
	 public IndexManager[] getIndexManagersForAllShards() {
	   return indexManagers;
	 }
	
	 public IndexManager getIndexManagerForAddition(
	     Class<?> entity, Serializable id, String idInString, Document document) {
	   Integer customerID = Integer.parseInt(document.getFieldable("customerID").stringValue());
	   return indexManagers[customerID];
	 }
	
	 public IndexManager[] getIndexManagersForDeletion(
	     Class<?> entity, Serializable id, String idInString) {
	   return getIndexManagersForAllShards();
	 }
	
	  /**
	  * Optimization; don't search ALL shards and union the results; in this case, we 
	  * can be certain that all the data for a particular customer Filter is in a single
	  * shard; simply return that shard by customerID.
	  */
	 public IndexManager[] getIndexManagersForQuery(
	     FullTextFilterImplementor[] filters) {
	   FullTextFilter filter = getCustomerFilter(filters, "customer");
	   if (filter == null) {
	     return getIndexManagersForAllShards();
	   }
	   else {
	     return new IndexManager[] { indexManagers[Integer.parseInt(
	       filter.getParameter("customerID").toString())] };
	   }
	 }
	
	 private FullTextFilter getCustomerFilter(FullTextFilterImplementor[] filters, String name) {
	   for (FullTextFilterImplementor filter: filters) {
	     if (filter.getName().equals(name)) return filter;
	   }
	   return null;
	 }
	}
この例では、custom という名前のフィルターがある場合、この顧客専用のシャードのみがクエリーされ、それ以外の場合はすべてのシャードが返されます。所定のシャード化ストラテジーは、単一または複数のフィルターに対応し、それらのパラメーターに依存します。
次のステップでは、クエリー時にフィルターをアクティブにします。フィルターは、クエリーの後に Lucene 結果をフィルターする (「フィルター」 で定義されている) 通常のフィルターですが、シャード化ストラテジーにのみ渡される特殊フィルターを使用することができます (その他は無視されます)。
この機能を使用するには、フィルターの宣言時に ShardSensitiveOnlyFilter クラスを指定します。
@Indexed
@FullTextFilterDef(name="customer", impl=ShardSensitiveOnlyFilter.class)
public class Customer {
   ...
}

FullTextQuery query = ftEm.createFullTextQuery(luceneQuery, Customer.class);
query.enableFulltextFilter("customer").setParameter("CustomerID", 5);
@SuppressWarnings("unchecked")
List<Customer> results = query.getResultList();
ShardSensitiveOnlyFilter を使用して Lucene フィルターを実装する必要はありません。シャード化された環境のクエリーを加速するには、これらのフィルターに対応するフィルターおよびシャード化ストラテジーを使用することが推奨されます。