2011-03-19 36 views
11

Estoy intentando crear una mejor función de autocompletar para mi sitio web. Quiero utilizar Hibernate Search para esto, pero por lo que he experimentado, solo encuentra palabras completas para mí.Autocompletar utilizando Hibernate Search

Entonces, mi pregunta: ¿es posible buscar solo algunos caracteres?

por ejemplo. el usuario escribe 3 letras y usa la búsqueda de hibernación para mostrarle todas las palabras de mis objetos db que contienen esas 3 letras?

PS. ahora estoy usando una consulta "me gusta" para esto ... pero mi DB creció mucho y también quiero ampliar la funcionalidad de búsqueda en otras tablas ...

Respuesta

6

Puede indexar el campo usando NGramFilter según lo sugerido here. Para obtener los mejores resultados, debe usar el EdgeNgramFilter de Apache Solr que crea ngrams desde el principio de un término y también se puede utilizar en la búsqueda de hibernación.

11

edición mayor Un año después, y yo era capaz de mejorar en el código original que he publicado para producir esto:

Mi entidad indexada:

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

se tokenized Todos @Field s y almacenado en el índice; requerido para que esto funcione:
@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; 
} 

Hay algunas discusiones que estoy haciendo al final de manejar campos @IndexedEmbedded. Si no tiene esos, puede simplificar mucho el código simplemente proyectando searchFields, y omitiendo el documento & embeddedField handling.

Como antes: Esperemos que esto sea útil para la siguiente persona que se encuentre con esta pregunta. Si alguien tiene alguna crítica o mejoras en el código publicado anteriormente, siéntase libre de editar y hágamelo saber.


Edit3: El proyecto de este código fue tomado de lo ha sido desde código abierto; Aquí están las clases pertinentes:

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

+0

Otro problema es que los resultados aparecen solo para una palabra si busco, por ejemplo: "esguince" hay resultado, pero para "esguince de" no hay ninguno. ¿Hay alguna manera de manejar esto? – nanospeck

+0

TermTermination matching = onFields.matching (input.toLowerCase()); También creo que 'input.toLowerCase()' debería haber sido 'lowerCasedSearchTerm'. – nanospeck

+0

@nanospeck Estoy un poco confuso porque ha pasado un tiempo desde que trabajé en esto, pero con respecto a la frase "esguince" de la búsqueda, es posible que tengas que ajustar el 'WhitespaceTokenizerFactory' a otra cosa, ya que creo que interrumpe palabras individuales. – Tim

2

respuesta de Tim es brillante y me ayudaron a conseguir sobre la parte difícil. Funcionó solo para una sola consulta de palabras para mí. En caso de que alguien lo quiera, haga que funcione para búsquedas de frases. Simplemente reemplace todas las instancias de 'Término' con sus correspondientes 'Frase'. Estas son las líneas de reemplazo para el código de Tim que me sirvieron de algo.

// 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