Detalles del uso de Hibernate Search con bases de datos Lucene
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:
package com.autentia.adictos.hs.model;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Autor {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nombre;
private String apellidos;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
@Override
public String toString() {
return "Autor [nombre=" + nombre + ", apellidos=" + apellidos + "]";
}
}
package com.autentia.adictos.hs.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Noticia {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date fechaPublicacion;
private String titular;
private String entradilla;
/**
* El cuerpo de la noticia vendrá en HTML
*/
private String cuerpo;
@ManyToOne
@JoinColumn(name = "id_autor")
private Autor autor;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getFechaPublicacion() {
return fechaPublicacion;
}
public void setFechaPublicacion(Date fechaPublicacion) {
this.fechaPublicacion = fechaPublicacion;
}
public String getTitular() {
return titular;
}
public void setTitular(String titular) {
this.titular = titular;
}
public String getEntradilla() {
return entradilla;
}
public void setEntradilla(String entradilla) {
this.entradilla = entradilla;
}
public String getCuerpo() {
return cuerpo;
}
public void setCuerpo(String cuerpo) {
this.cuerpo = cuerpo;
}
public Autor getAutor() {
return autor;
}
public void setAutor(Autor autor) {
this.autor = autor;
}
@Override
public String toString() {
return "Noticia [titular=" + titular + ", entradilla=" + entradilla + ", fechaPublicacion=" + fechaPublicacion
+ ", autor=" + autor + "]";
}
}
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:
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.
package com.autentia.adictos.hs.test.utils;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import com.autentia.adictos.hs.dao.DaoFactory;
import com.autentia.adictos.hs.dao.HibernateDaoImpl;
import com.autentia.adictos.hs.model.Autor;
import com.autentia.adictos.hs.model.Noticia;
/**
* Clase para creación de datos de pruebas
*
* @author fjmpaez
*/
public class DataGenerator {
public static void createData() {
/*
* Inicializamos la factoría de sesiones de Hibernate
*/
HibernateDaoImpl dao = DaoFactory.getDao();
Autor autor1 = new Autor();
autor1.setNombre("Francisco Javier");
autor1.setApellidos("Martínez");
dao.persist(autor1);
Autor autor2 = new Autor();
autor2.setNombre("Roberto");
autor2.setApellidos("Canales Mora");
dao.persist(autor2);
Autor autor3 = new Autor();
autor3.setNombre("Juan");
autor3.setApellidos("Alonso Ramos");
dao.persist(autor3);
Noticia noticia1 = new Noticia();
noticia1.setTitular("Alberto Contador casi sentencia el Tour");
noticia1.setEntradilla("El ciclista español logró pulverizar "
+ "todos los registros en la crono y consigue su doblete en este "
+ "Tour de Francia ampliando diferencias con todos sus rivales. "
+ "Lance Armstrong vuelve al podio, donde ahora es tercero, pero a sólo a 11 segundos de Wiggins");
noticia1.setFechaPublicacion(new Date());
noticia1.setAutor(autor1);
noticia1.setCuerpo(readBody("cuerpo1.html"));
dao.persist(noticia1);
Noticia noticia2 = new Noticia();
noticia2.setTitular("Aumenta la tensión entre Lorenzo y Rossi");
noticia2.setEntradilla("Jorge Lorenzo no entiende las duras críticas de Valentino Rossi en Alemania. "
+ "Ve posible renovar con Yamaha, pero admite tener otras ofertas");
noticia2.setFechaPublicacion(new Date());
noticia2.setAutor(autor2);
noticia2.setCuerpo(readBody("cuerpo2.html"));
dao.persist(noticia2);
Noticia noticia3 = new Noticia();
noticia3.setTitular("Alonso: Queremos intentar confirmar que el coche ha mejorado");
noticia3.setEntradilla("Queremos intentar confirmar que el coche ha mejorado, "
+ "que podemos estar en niveles competitivos y que las vueltas rápidas de Nurburgring "
+ "no fueron producto de las temperaturas bajas o cualquier otro motivo raro");
noticia3.setFechaPublicacion(new Date());
noticia3.setAutor(autor3);
noticia3.setCuerpo(readBody("cuerpo3.html"));
dao.persist(noticia3);
}
/**
* Lee el texto del fichero y lo devuelve
*
* @param fileName
* @return
*/
private static String readBody(String fileName) {
StringBuilder sb = new StringBuilder();
FileReader fr = null;
try {
fr = new FileReader(DataGenerator.class.getClassLoader().getResource(fileName).getPath());
char[] c = new char[256];
int cars = fr.read(c);
while (cars != -1) {
sb.append(c);
cars = fr.read(c);
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
DaoFactory será la clase encargada de construir Daos.
package com.autentia.adictos.hs.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.context.ThreadLocalSessionContext;
public final class DaoFactory {
private static HibernateDaoImpl dao;
private DaoFactory() {
// Clase de utilidad, singleton, no se pueden crear instancias
}
public static void init() {
HibernateIgniter.init();
final SessionFactory sessionFactory = HibernateIgniter.getSessionFactory();
dao = new HibernateDaoImpl(sessionFactory);
}
public static void close() {
HibernateIgniter.close();
}
static void openSession() {
final Session sess = HibernateIgniter.getSessionFactory().openSession();
ThreadLocalSessionContext.bind(sess);
}
static void closeSession() {
HibernateIgniter.getSessionFactory().getCurrentSession().close();
ThreadLocalSessionContext.unbind(HibernateIgniter.getSessionFactory());
}
public static HibernateDaoImpl getDao() {
return dao;
}
}
5. Anotando las clases para la indexación.
Para preparar nuestra clase Noticia para indexación haremos las siguientes cosas:
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.
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.
Cada atributo que queramos indexar lo marcaremos como @Field. Algunos nos interesarán que sean tokenizados (titular, entradilla y cuerpo) y otros no (fecha).
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)
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.
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).
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.
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:
package com.autentia.adictos.hs.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.solr.analysis.HTMLStripStandardTokenizerFactory;
import org.apache.solr.analysis.ISOLatin1AccentFilterFactory;
import org.apache.solr.analysis.LowerCaseFilterFactory;
import org.apache.solr.analysis.SnowballPorterFilterFactory;
import org.apache.solr.analysis.StopFilterFactory;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.Boost;
import org.hibernate.search.annotations.DateBridge;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Parameter;
import org.hibernate.search.annotations.Resolution;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TokenFilterDef;
import org.hibernate.search.annotations.TokenizerDef;
@Entity
@Indexed
@AnalyzerDef(name = "Analizador_Noticia", tokenizer = @TokenizerDef(factory = HTMLStripStandardTokenizerFactory.class), filters = {
@TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class, params = {
@Parameter(name = "words", value = "spanish-stoplist.txt"),
@Parameter(name = "ignoreCase", value = "true") }),
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = { @Parameter(name = "language", value = "Spanish") }) })
public class Noticia {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@DocumentId
private Long id;
@Temporal(TemporalType.TIMESTAMP)
@Field(index = Index.UN_TOKENIZED, store = Store.NO)
@DateBridge(resolution = Resolution.DAY)
@Analyzer(definition = "Analizador_Noticia")
private Date fechaPublicacion;
@Field(index = Index.TOKENIZED, store = Store.NO)
@Boost(2.0f)
@Analyzer(definition = "Analizador_Noticia")
private String titular;
@Field(index = Index.TOKENIZED, store = Store.NO)
@Boost(1.5f)
@Analyzer(definition = "Analizador_Noticia")
private String entradilla;
/**
* El cuerpo de la noticia vendrá en HTML
*/
@Field(index = Index.TOKENIZED, store = Store.NO)
@Analyzer(definition = "Analizador_Noticia")
private String cuerpo;
@ManyToOne
@JoinColumn(name = "id_autor")
@IndexedEmbedded
private Autor autor;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getFechaPublicacion() {
return fechaPublicacion;
}
public void setFechaPublicacion(Date fechaPublicacion) {
this.fechaPublicacion = fechaPublicacion;
}
public String getTitular() {
return titular;
}
public void setTitular(String titular) {
this.titular = titular;
}
public String getEntradilla() {
return entradilla;
}
public void setEntradilla(String entradilla) {
this.entradilla = entradilla;
}
public String getCuerpo() {
return cuerpo;
}
public void setCuerpo(String cuerpo) {
this.cuerpo = cuerpo;
}
public Autor getAutor() {
return autor;
}
public void setAutor(Autor autor) {
this.autor = autor;
}
@Override
public String toString() {
return "Noticia [titular=" + titular + ", entradilla=" + entradilla + ", fechaPublicacion=" + fechaPublicacion
+ ", autor=" + autor + "]";
}
}
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.
package com.autentia.adictos.hs.test.utils;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import com.autentia.adictos.hs.dao.DaoFactory;
import com.autentia.adictos.hs.dao.HibernateDaoImpl;
import com.autentia.adictos.hs.model.Autor;
import com.autentia.adictos.hs.model.Noticia;
/**
* Clase para creación de datos de pruebas
*
* @author fjmpaez
*/
public class DataGenerator {
public static void createData() {
/*
* Inicializamos la factoría de sesiones de Hibernate
*/
HibernateDaoImpl dao = DaoFactory.getDao();
Autor autor1 = new Autor();
autor1.setNombre("Francisco Javier");
autor1.setApellidos("Martínez");
dao.persist(autor1);
Autor autor2 = new Autor();
autor2.setNombre("Roberto");
autor2.setApellidos("Canales Mora");
dao.persist(autor2);
Autor autor3 = new Autor();
autor3.setNombre("Juan");
autor3.setApellidos("Alonso Ramos");
dao.persist(autor3);
Noticia noticia1 = new Noticia();
noticia1.setTitular("Alberto Contador casi sentencia el Tour");
noticia1.setEntradilla("El ciclista español logró pulverizar "
+ "todos los registros en la crono y consigue su doblete en este "
+ "Tour de Francia ampliando diferencias con todos sus rivales. "
+ "Lance Armstrong vuelve al podio, donde ahora es tercero, pero a sólo a 11 segundos de Wiggins");
noticia1.setFechaPublicacion(new Date());
noticia1.setAutor(autor1);
noticia1.setCuerpo(readBody("cuerpo1.html"));
dao.persist(noticia1);
Noticia noticia2 = new Noticia();
noticia2.setTitular("Aumenta la tensión entre Lorenzo y Rossi");
noticia2.setEntradilla("Jorge Lorenzo no entiende las duras críticas de Valentino Rossi en Alemania. "
+ "Ve posible renovar con Yamaha, pero admite tener otras ofertas");
noticia2.setFechaPublicacion(new Date());
noticia2.setAutor(autor2);
noticia2.setCuerpo(readBody("cuerpo2.html"));
dao.persist(noticia2);
Noticia noticia3 = new Noticia();
noticia3.setTitular("Alonso: Queremos intentar confirmar que el coche ha mejorado");
noticia3.setEntradilla("Queremos intentar confirmar que el coche ha mejorado, "
+ "que podemos estar en niveles competitivos y que las vueltas rápidas de Nurburgring "
+ "no fueron producto de las temperaturas bajas o cualquier otro motivo raro");
noticia3.setFechaPublicacion(new Date());
noticia3.setAutor(autor3);
noticia3.setCuerpo(readBody("cuerpo3.html"));
dao.persist(noticia3);
}
/**
* Lee el texto del fichero y lo devuelve
*
* @param fileName
* @return
*/
private static String readBody(String fileName) {
StringBuilder sb = new StringBuilder();
FileReader fr = null;
try {
fr = new FileReader(DataGenerator.class.getClassLoader().getResource(fileName).getPath());
char[] c = new char[256];
int cars = fr.read(c);
while (cars != -1) {
sb.append(c);
cars = fr.read(c);
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
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:
package com.autentia.adictos.hs.test;
import java.util.List;
import junit.framework.Assert;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.autentia.adictos.hs.dao.DaoFactory;
import com.autentia.adictos.hs.dao.HibernateDaoImpl;
import com.autentia.adictos.hs.model.Noticia;
import com.autentia.adictos.hs.test.utils.DataGenerator;
public class SearchNoticiasTest {
public static final String[] whereToSearch = { "titular", "entradilla", "cuerpo", "autor.nombre", "autor.apellidos" };
@BeforeClass
public static void startUp() {
DaoFactory.init();
DataGenerator.createData();
}
@AfterClass
public static void tearDown() {
DaoFactory.close();
}
@Test
public void test1HayNoticias() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.loadAll(Noticia.class);
Assert.assertTrue("El número de noticias total no coincide", noticias.size() == 3);
}
}
A continuación el resto de los tests que se explican en cada uno de ellos.
...
/*
* Búsqueda normal por palabras. Buscamos las palabras aumentar y lograr, que no están pero si aumentó y logró
*/
@Test
public void test2BusquedaSencilla() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.findByFullText(Noticia.class, whereToSearch, "aumentar lograr",
"Analizador_Noticia", "fechaPublicacion");
Assert.assertTrue("El número de noticias encontradas no coincide", noticias.size() == 2);
}
// Búsqueda normal por palabras. Busco en el cuerpo il dottore.
@Test
public void test3BusquedaSencilla() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.findByFullText(Noticia.class, whereToSearch, "il dottore", "Analizador_Noticia",
"fechaPublicacion");
Assert.assertTrue("El número de noticias encontradas no coincide", noticias.size() == 1);
}
// Búsqueda por Autor. Busco Martínez Páez (no me preocupo de los acentos, porque al pasarle
// el "Analizador_Noticia" Hibernate Search se encarga de preparar las palabras de manera correcta.
@Test
public void test4BusquedaPorAutor() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.findByFullText(Noticia.class, whereToSearch, "Martínez Páez",
"Analizador_Noticia", "fechaPublicacion");
Assert.assertTrue("El número de noticias encontradas no coincide", noticias.size() == 1);
}
// Busco palabras que no debe de haber indexado.
@Test
public void test5BusquedaStopWord() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.findByFullText(Noticia.class, whereToSearch, "éste esta este algunos alguna",
"Analizador_Noticia", "fechaPublicacion");
Assert.assertTrue("El número de noticias encontradas no coincide", noticias.size() == 0);
}
// Busco palabras que no debe de haber indexado.
@Test
public void test6BusquedaHTML() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.findByFullText(Noticia.class, whereToSearch, "
href
",
"Analizador_Noticia", "fechaPublicacion");
Assert.assertTrue("El número de noticias encontradas no coincide", noticias.size() == 0);
}
// Busco por rango de fechas. [20080101 TO 20101231]
// Desde el 1 de enero de 2008 hasta el 31 de diciembre de 2010.
@Test
public void test7BusquedaFechas() {
HibernateDaoImpl dao = DaoFactory.getDao();
List noticias = dao.findByFullText(Noticia.class, whereToSearch, "[20080101 TO 20101231]",
"Analizador_Noticia", "fechaPublicacion");
Assert.assertTrue("El número de noticias encontradas no coincide", noticias.size() == 3);
}
...
Comentarios
Un comentario
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:
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?
Detecta emociones en tiempo real con visión artificial y machine learning usando MediaPipe, CVZone y scikit-learn en solo unos cientos de líneas de código.
Un comentario
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?