Hibernate Search, Bridges, Analizadores y más

1
8949

Hibernate Search, Bridges, Analizadores y más

En este tutorial vamos a tratar de comentar algunos detalles un poco más avanzados cuando trabajamos con Hibernate Search.
Trataré de explicar qué es un analizador, qué es un filtro, cómo podemos anotar las entidades del modelo con respecto a
Hibernate Search para poder buscar en entidades relacionadas, etc…

1. Introducción.

Si estás leyendo este tutorial, probablemente ya tengas nociones acerca de lo que es una base de datos inversa como Lucene y las posibilidades que ofrece.
Básicamente podríamos describir una base de datos de este tipo como palabra-céntrica o token-céntrica (terminos que no existen).

Es decir, aunque este tipo de Bases de Datos almacenan lo que denominan Documentos, estos son organizados en referencia a lo que se denominan tokens o términos.
Cualquier cosa puede ser considerada un documento siempre que se pueda convertir su información a texto (única cosa que entienden estas bases de datos).

Alguno dirá ¿ Y si no se puede convertir a texto ? Y yo le respondo, ¿ Hay algo que no se pueda describir con palabras ?.

Por lo tanto, lo primero que ha de hacer una base de datos de este tipo es analizar la información que se quiere almacenar, convertirla a texto, tokenizarla o separararla en términos que luego podrán ser usados para las búsquedas y relacionar
los documentos con esos términos o tokens. En este proceso de conversión a texto y tokenización o separación en términos de búsqueda es donde entran los bridges, los analizadores y los filtros.

2. Presentación del ejemplo.

Antes de empezar os dejo un zip con los fuentes del tutorial aquí

Dispongo de una base de datos relacional que contiene básicamente Noticias y Autores de esas noticias. Estoy usando actualmente Hibernate
trabajar con esta información. A continuación os muestro las entidades que describen este modelo de datos:

Hasta este momento, yo he sido feliz creando, modificando y eliminando noticias y autores.
Pero el cliente me ha pedido que si el podría buscar noticias como cuando busca en google.
Es decir el pone una palabrita en una cajita de texto y el sistema le devuelve aquellas noticias que contengan
esa palabrita en cualquier lugar (cuerpo, entradilla, autor, fecha …)
Evidentemente, tratar de realizar esto a través de HQL (o SQL) puede ser muy lento y probablemente no dé los resultados que espero.
¿ Puedo seguir siendo feliz ?. Espero que si.

3. Maven y sus cosicas

Vamos ahora configurar el proyecto con ayuda de Maven para poder obtener las herramientas que necesitamos. Os muestro el pom.xml:

4. Configuración de Hibernate, el Dao y preparación de las pruebas.

A continuación configuraremos Hibernate (hibernate.cfg.xml)

Vamos ahora a crear una clase de utilidades para cargar los datos al inicio de las pruebas. He creado tres ficheros html que serán el cuerpo de las noticias y que leeré también durante la carga de datos y guardaré en las noticias.
Esta clase se apoya en un conjunto de clases que forman el Dao.

A continuación las clases del Dao

El interfaz HibernateCallback.

La clase HibernateDaoImpl que será un Dao un poco limitado. A nosotros, para este tutorial únicamente nos interesa el método findByFullText

HibernateIgniter será la clase que se encargue de inicializar la factoría de sesiones de Hibernate

DaoFactory será la clase encargada de construir Daos.

5. Anotando las clases para la indexación.

Para preparar nuestra clase Noticia para indexación haremos las siguientes cosas:

  1. La marcaremos como @Indexed para indicar a Hibernate Search que nuestra clase es indexable. Desde este momento y al estar en el classpath Hibernate Search,
    se activarán los listeners de indexación cada vez que se modifique o se guarde una entidad de este tipo.
  2. Seleccionaremos el atributo que queremos marcar como identificador en lucene marcándolo como @DocumentId. Lo normal es que sea el mismo que es el @Id de Hibernate.
  3. Cada atributo que queramos indexar lo marcaremos como @Field. Algunos nos interesarán que sean tokenizados (titular, entradilla y cuerpo) y otros no (fecha).
  4. El atributo fecha ha de ser pasado a String durante la indexación. Usaremos un Bridge de fechas @DateBridge que incluye Hibernate Search y le diremos que queremos sólo indexar
    como máxima resolución a día (no nos interesa la hora exacta)
  5. Daremos más relevancia a las ocurrencias del titular, después a la entradilla y por último el cuerpo. Esto lo haremos usando el atributo @Boost y
    entraría en juego si le pidiéramos ordenar por relevancia.
  6. Marcaremos el atributo autor para ser indexado juntamente con Noticia (para poder buscar noticias por autor). Esto lo haremos con la
    anotación @IndexEmbedded. En el otro lado, en la clase autor, para que Hibernate Search reindexe una noticia cuando un autor sea modificado marcaremos
    el atributo noticias de la clase autor como @ContainedIn (esto nos obliga a marcar la relación como bidireccional. Si no lo hiciésemos así, deberíamos ser nosotros
    los encargados de reindexar una noticia cuando un autor sea modificado).
  7. Además definiremos un Analizador que denominaremos «Analizador_Noticia». Nuestro analizador estará formado por un conjunto de
    Filtros creados por el proyecto solr y lucene-snowbal que complementarán la forma en la que serán indexados nuestros atributos.
    Nuestro analizador contiene los siguientes filtros:

    • HTMLStripStandardTokenizerFactory: Extrae el texto del HTML. Ideal para el atributo cuerpo que es una página HTML
    • ISOLatin1AccentFilterFactory: Elimina acentos, diéresis etc… durante la tokenización
    • StopFilterFactory: No indexa todas aquellas palabras que contenga el fichero indicado
    • LowerCaseFilterFactory: Pasa a minúsculas todos los textos
    • SnowballPorterFilterFactory: Analiza las palabras para extraer la raíz de la misma y buscar palabras con la misma raíz (gato, gata)

    Todo esto lo haremos con la anotación @AnalyzerDef.

  8. Por último indicaremos en nuestros atributos que use el Analizador definido durante la indexación. Esto lo haremos con la anotación @Analyzer

A continuación muestro como quedan nuestras clases:

6. Probando todo esto.

Vamos ahora a crear una clase de test unitarios que me permita probar todo esto y una clase para inicializar los datos de prueba.

Crearemos tres autores y tres noticias. Los cuerpos de las noticias los he descargado de un periódico de deportes en html y lo leo del disco.
La clase que crea los datos de prueba.

Y por último empezaremos con los Tests.
Primero inicializaremos Hibernate y crearemos los datos al comienzo de los tests.
Luego creamos nuestro primer test para comprobar que realmente se han creado las noticias:

A continuación el resto de los tests que se explican en cada uno de ellos.

1 Comentario

  1. Hola Francisco Javier. En primer lugar, enhorabuena por los éxitos logrados.

    Necesito tu ayuda, por favor:

    Tengo una tabla (prueba) con 2 campos, un índice (id) y un campo TEXT (descripcion) indexado como fulltext en mysql.

    Estoy trabajando con Java 1.8, hibernate 5, spring en una aplicación MVC que funciona perfectamente. He logrado encajar las versiones y compila sin errores (y este es el problema.. que no da errores).

    Pretendo hacer un ejemplo fácil para hacer una búsqueda en la tabla prueba usando el índice fulltext desde hibernate. He seguido tu ejemplo (y unos 70 u 80 más de internet) y en todos el efecto es el mismo. Cuando hago esto:

    FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
    QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Prueba.class).get();
    org.apache.lucene.search.Query luceneQuery = queryBuilder
    .keyword().wildcard()
    .onFields(«descripcion»)
    .matching(«hola»)
    .createQuery();
    org.hibernate.Query queryfull = fullTextSession.createFullTextQuery(luceneQuery, Prueba.class);
    List list = (List) queryfull.list();

    el array «list» está vacío (y es imposible, puesto que hay coincidencias). Si hago un System.out.println(queryfull);, me muestra esto: «descripcion»:»hola».

    No da ningún error, ni el compilador, ni netbeans, ni hibernate, ni javascript.. nada!!! símplemente devuelve un list vacío. Estoy desesperado. ¿Qué estoy haciendo mal?

Dejar respuesta

Please enter your comment!
Please enter your name here