11

Web サイトのオートコンプリート機能を改善しようとしています。これには Hibernate Search を使用したいのですが、実験した限りでは完全な単語しか見つかりません。

それで、私の質問: 一部の文字のみを検索することは可能ですか?

例えば。ユーザーは3文字を入力し、休止状態の検索を使用して、それらの3文字を含む私のdbオブジェクトのすべての単語を彼に表示しますか?

PS。現在、これに「like」クエリを使用しています...しかし、私のデータベースは大きくなり、別のテーブルにも検索機能を拡張したいと考えています...

4

3 に答える 3

12

主要な編集 1年後、投稿した元のコードを改善して、これを生成することができました。

私のインデックス付きエンティティ:

@Entity
@Indexed
@AnalyzerDef(name = "myanalyzer",
// Split input into tokens according to tokenizer
tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class), //
filters = { //
// Normalize token text to lowercase, as the user is unlikely to care about casing when searching for matches
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
// Index partial words starting at the front, so we can provide Autocomplete functionality
@TokenFilterDef(factory = NGramFilterFactory.class, params = { @Parameter(name = "maxGramSize", value = "1024") }),
// Close filters & Analyzerdef
})
@Analyzer(definition = "myanalyzer")
public class Compound extends DomainObject {
public static String[] getSearchFields(){...}
...
}

すべて@Fieldの はトークン化され、インデックスに格納されます。これが機能するために必要:
@Field(index = Index.TOKENIZED, store = Store.YES)

@Transactional(readOnly = true)
public synchronized List<String> getSuggestions(final String searchTerm) {
    // Compose query for term over all fields in Compound
    String lowerCasedSearchTerm = searchTerm.toLowerCase();

    // Create a fullTextSession for the sessionFactory.getCurrentSession()
    FullTextSession fullTextSession = Search.getFullTextSession(getSession());

    // New DSL based query composition
    SearchFactory searchFactory = fullTextSession.getSearchFactory();
    QueryBuilder buildQuery = searchFactory.buildQueryBuilder().forEntity(Compound.class).get();
    TermContext keyword = buildQuery.keyword();
    WildcardContext wildcard = keyword.wildcard();
    String[] searchfields = Compound.getSearchfields();
    TermMatchingContext onFields = wildcard.onField(searchfields[0]);
    for (int i = 1; i < searchfields.length; i++)
        onFields.andField(searchfields[i]);
    TermTermination matching = onFields.matching(input.toLowerCase());
    Query query = matching.createQuery();

    // Convert the Search Query into something that provides results: Specify Compound again to be future proof
    FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(query, Compound.class);
    fullTextQuery.setMaxResults(20);

    // Projection does not work on collections or maps which are indexed via @IndexedEmbedded
    List<String> projectedFields = new ArrayList<String>();
    projectedFields.add(ProjectionConstants.DOCUMENT);
    List<String> embeddedFields = new ArrayList<String>();
    for (String fieldName : searchfields)
        if (fieldName.contains("."))
            embeddedFields.add(fieldName);
        else
            projectedFields.add(fieldName);

    @SuppressWarnings("unchecked")
    List<Object[]> results = fullTextQuery.setProjection(projectedFields.toArray(new String[projectedFields.size()])).list();

    // Keep a list of suggestions retrieved by search over all fields
    List<String> suggestions = new ArrayList<String>();
    for (Object[] projectedObjects : results) {
        // Retrieve the search suggestions for the simple projected field values
        for (int i = 1; i < projectedObjects.length; i++) {
            String fieldValue = projectedObjects[i].toString();
            if (fieldValue.toLowerCase().contains(lowerCasedSearchTerm))
                suggestions.add(fieldValue);
        }

        // Extract the search suggestions for the embedded fields from the document
        Document document = (Document) projectedObjects[0];
        for (String fieldName : embeddedFields)
            for (Field field : document.getFields(fieldName))
                if (field.stringValue().toLowerCase().contains(lowerCasedSearchTerm))
                    suggestions.add(field.stringValue());
    }

    // Return the composed list of suggestions, which might be empty
    return suggestions;
}

@IndexedEmbedded フィールドを処理するために最後に行っているいくつかの論争があります。それらがない場合は、searchFields を射影し、ドキュメントと embeddedField の処理を​​省略するだけで、コードを大幅に簡素化できます。

前と同じように: これが、この質問に遭遇する次の人に役立つことを願っています。上記の投稿されたコードに対する批判や改善があれば、自由に編集して、私に知らせてください。


Edit3 : このコードが取得されたプロジェクトは、その後オープン ソース化されました。関連するクラスは次のとおりです。

https://trac.nbic.nl/metidb/browser/trunk/metidb/metidb-core/src/main/java/org/metidb/domain/Compound.java
https://trac.nbic.nl/metidb/browser /trunk/metidb/metidb-core/src/main/java/org/metidb/dao/CompoundDAOImpl.java
https://trac.nbic.nl/metidb/browser/trunk/metidb/metidb-search/src/main/ java/org/metidb/search/text/Autocompleter.java

于 2011-06-29T09:37:34.120 に答える
7

ここで提案されているように、 NGramFilterを使用してフィールドにインデックスを付けることができます。最良の結果を得るには、Apache Solr のEdgeNgramFilterを使用する必要があります。この EdgeNgramFilterは、用語の先頭エッジから ngram を作成し、休止状態の検索でも使用できます。

于 2011-03-19T13:17:14.013 に答える
2

ティムの答えは素晴らしく、難しい部分を乗り越えるのに役立ちました。私にとっては、単一の単語クエリでしか機能しませんでした。フレーズ検索で機能させたい場合に備えて。すべての「Term」インスタンスを対応する「Phrase」クラスに置き換えるだけです。これは、私のためにトリックを行ったTimのコードの置換行です。

// New DSL based query composition
            //org.hibernate.search.query.dsl
            SearchFactory searchFactory = fullTextSession.getSearchFactory();
            QueryBuilder buildQuery = searchFactory.buildQueryBuilder().forEntity(MasterDiagnosis.class).get();
            PhraseContext keyword = buildQuery.phrase();
            keyword.withSlop(3);
            //WildcardContext wildcard = keyword.wildcard();
            String[] searchfields = MasterDiagnosis.getSearchfields();
            PhraseMatchingContext onFields = keyword.onField(searchfields[0]);
            for (int i = 1; i < searchfields.length; i++)
                onFields.andField(searchfields[i]);
            PhraseTermination matching = onFields.sentence(lowerCasedSearchTerm);
            Query query = matching.createQuery();
 // Convert the Search Query into something that provides results: Specify Compound again to be future proof
于 2013-05-08T11:42:15.363 に答える