【问题标题】:Autocomplete using Hibernate Search使用 Hibernate Search 自动完成
【发布时间】:2011-07-18 17:31:47
【问题描述】:

我正在尝试为我的网站构建更好的自动完成功能。我想为此使用 Hibernate Search,但据我试验,它只能为我找到完整的单词。

所以,我的问题是:是否可以只搜索某些字符?

例如。用户键入 3 个字母并使用休眠搜索向他显示包含这 3 个字母的我的数据库对象的所有单词?

附言。现在我正在为此使用“喜欢”查询...但是我的数据库增长了很多,我还想将搜索功能扩展到其他表...

【问题讨论】:

    标签: java mysql hibernate autocomplete hibernate-search


    【解决方案1】:

    Tim 的回答非常出色,帮助我克服了困难。它只适用于我的一个单词查询。如果有人想让它用于短语搜索。只需将所有“术语”实例替换为相应的“短语”类即可。以下是 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
    

    【讨论】:

      【解决方案2】:

      主要修改 一年过去了,我能够改进我发布的原始代码来生成这个:

      我的索引实体:

      @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(){...}
      ...
      }
      

      所有@Fields 都被标记并存储在索引中;需要此功能:
      @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

      【讨论】:

      • 另一个问题是,如果我搜索例如:“sprain”有结果但“sprain of”没有结果,则结果仅显示为单个单词。有没有办法解决这个问题?
      • 终止匹配 = onFields.matching(input.toLowerCase());我还认为“input.toLowerCase()”应该是“lowerCasedSearchTerm”。
      • @nanospeck 我有点模糊,因为我已经有一段时间没有研究这个了,但是关于短语搜索“sprain of”,您可能需要将 WhitespaceTokenizerFactory 调整为其他内容,因为我认为这会破坏单个单词。
      • @nanospeck 关于您对lowerCasedSearchTerm 的第二条评论:这可能是有效的,请随时更新或发布您自己的代码!
      【解决方案3】:

      您可以按照建议的here 使用NGramFilter 为该字段编制索引。为获得最佳结果,您应该使用来自 Apache Solr 的 EdgeNgramFilter,它从术语的开始边缘创建 ngram,也可用于休眠搜索。

      【讨论】:

        猜你喜欢
        • 2021-01-23
        • 2012-02-28
        • 2012-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-31
        • 2021-05-29
        相关资源
        最近更新 更多