Hibernate Search com Lucene
Postado em 13. jun, 2007 por Nico Steppat em Java
O Hibernate Search é uma “full text search engine” que tem sua implementação escrita em cima do Apache Lucene.
O Lucene por si só já é bem famoso pelas funcionalidades oferecidas, como buscas aproximadas, índices em memória, e velocidade. A parte mais complicada é a configuração e preparação (indexação) dos dados que são necessários para executar a busca. Aqui entra o Hibernate Search: ele te poupa desse trabalho e integra o Lucene muito bem com o Hibernate, dada a arquitetura de listeners e callbacks deste projeto. Com ele podemos buscar textualmente pelas entidades, usando as funcionalidades do Lucene sem a dor de cabeça da criação de Documents, definição dos tipos de Fields, controle de acesso concorrente aos índices e outros pequenos detalhes que aparecem ao usar o projeto diretamente. Vamos ver um exemplo simples como configurar e usar o Hibernate Search.
Anotações
Tanto nas Hibernate Annotations quanto no Hibernate Search você passa a maioria das configurações usando anotações no seu modelo de domínio. Para poder busca uma entidade você deve anota-lá com @Indexed. Usando esta anotação o Lucene irá analisar os dados da entidade e indexa-los, pois um interceptador do Hibernate fará esse trabalho por de trás dos panos. Quais dados você pretende indexar e como deve ser especificado anotando o atributo com @Field. Se você não usar nenhuma anotação nenhum dado será indexado.
@Entity
@Indexed
public class Dvd {
@Id @GeneratedValue
private Long id;
@Field(index=Index.TOKENIZED,store=Store.YES)
private String title;
@Field(index=Index.TOKENIZED)
private String description;
// possivelmente alguns getters e setters
}
Index
Usamos no exemplo os atributos index e store na anotação @Field. Quando Lucene indexa um campo, ele salva os dados num formato especial para ele poder executar suas buscas. Por exemplo, se você usa o título “Rambo 2″, o Lucene indexa Rambo e 2 separado. Esse tipo de indexação se chama TOKENIZED. Outros tipos são Index.NO (não será indexado e não tem como buscar) e Index.UN_TOKENIZED (não será “separado”, útil para campos considerado chaves). Essa nomenclatura é bem familiar para quem já usou o Lucene diretamente.
Store
Indica que Lucene deve gravar os dados (o título do DVD, no caso) no índice, de tal forma que esse valor possa ser recuperado integralmente diretamente pelos indices. Quando Lucene procura o título, ele pode devolver diretamente o valor, porque usamos Store.YES. É mais rápido, pois o hibernate não precisará consultar o banco para buscar aquele atributo, mas precisa mais espaço no disco e de certo forma é uma redundância de informação.
Eventos e diretórios
Hibernate Search encapsula o trabalho de indexação. Somente precisamos avisar o Hibernate Search em que momento nós desejaremos indexar as entidades em questão. Para isso existem listeners que podem ser configurados no hibernate.cfg.xml:
<session-factory>
...
<event type="post-update">
<listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
</event>
<event type="post-insert">
<listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
</event>
<event type="post-delete">
<listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
</event>
...
</session-factory>
Definimos 3 listeners globais. Hibernate os ativa automaticamente quando o tipo de evento acontecer. Nesse caso qualquer alteração numa entidade faz com que o Lucene altere o índice.
Além disso temos que definir um provedor que cria os arquivos e diretórios que serão usados para os índices do Lucene:
<session-factory>
...
<property name="hibernate.search.default.directory_provider">
org.hibernate.search.store.FSDirectoryProvider
</property>
<property name="hibernate.search.default.indexBase">
/home/nico/workspace/mydvds/webapp/indexes
</property>
...
</session-factory>
Existem outros provedores como RAMDirectoryProvider que somente grava na memória.
A busca
Configurado o Hibernate Search, vamos criar a busca. Você também usa um objeto session/query para fazer suas buscas, na verdade a sessão do Hibernate Search encapsula uma do hibernate. Para criar uma sessão usamos o método estático createFullTextSession da classe org.hibernate.search.Search e passamos a sessão do Hibernate como argumento:
FullTextSession LuceneSession =
Search.createFullTextSession(hibernateSession);
Com a sessão na mão podemos criar um org.hibernate.search.FullTextQuery do hibernate search:
FullTextQuery fullTextQuery =
LuceneSession.createFullTextQuery(LuceneQuery);
FullTextQuery estende Query do hibernate. Podemos invocar fullTextQuery.list() para receber a lista de objetos (neste caso dvds) baseado na query do Lucene.
Para criar essa query, usamos a api do Lucene. Ela oferece uma sintaxe simples e muito poderosa para definir nosso critério de busca. Essa sintaxe é verificada pelo QueryParser do Lucene:
String[] stopWords = new String[]{"de","do","da","dos","das","a","o","na","no","em"};
QueryParser parser = new QueryParser("title",new StopAnalyzer(stopWords));));
Queremos procurar no campo title e passamos um objeto Analyzer. Ele é responsável por filtrar palavras insignificante na busca que foram definidos no array stopWords. Por padrão o StopAnalyzer somente conhece “stop words” do idioma inglês. É claro que o array deveria ser bem maior. Existem outros analyzers, como o BrazilianAnalyzer, que podem te ajudar bastante na tarefa de separar o que deve e como deve ser indexado.
Com o parser na mão podemos finalmente criar a query:
Query LuceneQuery = parser.parse("Deus");
procura por “deus” ou “Deus” e encontra o título “Cidade de Deus” por exemplo. Uma busca por “de” encontra nada, porque definimos “de” como uma stopword. Você pode fazer buscas muito mais interessantes, como “+cidade -Deus”, que busca por todos os dvds que possuem “cidade” mas não possuem “Deus” como título.
Interessante também é a possibilidade de fazer uma busca aproximada, as tão faladas fuzzy searches. Se você buscar por “cidadi~”, a query também devolverá o mesmo dvd como resultado da query! O código final ficaria:
public void search(Dvd dvd) throws ParseException {
Session session = this.factory.getSession();
FullTextSession LuceneSession = Search.createFullTextSession(session);
String[] stopWords = {"de","do","da","dos","das","a","o","na","no","em"};
QueryParser parser = new QueryParser("title",new StopAnalyzer(stopWords));
Query LuceneQuery = parser.parse(dvd.getTitle());
FullTextQuery fullTextQuery = LuceneSession.createFullTextQuery(LuceneQuery, Dvd.class);
this.dvds = fullTextQuery.list();
}
Repare que mesmo a query sendo do Hibernate Search, ela devolve uma lista de objetos managed de DVDs. Qualquer alteração deles acarretará em uma mudança no banco de dados e reindexação dos campos!
Conclusão
Essa foi simente uma introdução do Hibernate Search e Lucene. Hibernate Search facilita ainda mais a configuração e indexação pelo Lucene: possibilita, por exemplo, usar JMS para guardar os índices de maneira clusterizada, além de indexar os documentos assíncronamente. Também é simples fazer ordenações e projeções, combinações com criteria, mapeamento de relacionamento, boost factors, otimização dos índices, e muitos mais. Você pode também utilizar o Hibernate Lucene através do EntityManager da JPA.
ASSINE NOSSO RSS
Daniel F. Martins
14. jun, 2007
Bom post! Vou pesquisar mais detalhes a respeito, pois é bastante interessante…
Luca Bastos
14. jun, 2007
Link (que eu ainda não li) para apreciaçao de V.Sas:
http://jroller.com/page/sjivan?entry=hibernate_search_cool_but_is
Paulo Silveira
14. jun, 2007
Luca, o Tiago Silveira já falou bastate dessa abordagem aqui. Realmente essa abordagem por anotação pode poluir um pouco seu código, e algumas vezes queremos indexar o mesmo item de diversas formas diferentes, o que fica impraticavel com o Hibernate Search. Vantagens e desvantagens…
Rafael Naufal
15. jun, 2007
Muito bom o artigo. Você pode ter os benefícios do mapeamento objeto-relacional provido pelo Hibernate e usar o mesmo mecanismo de anotação para anotar as entidades e seus atributos juntamente para serem indexadas pela engine do Lucene. Bom trabalho pessoal. Vou buscar mais detalhes dessa integração.
Gustavo Rodriges
24. jul, 2007
Olá Nico,
Baixei a aplicação mydvds (com as alterações que você fez na busca ajax), mas estou tento problemas para salvar no banco (estou usando o MySQL) com as anotações do Hibernate Search.
Abri um tópico no GUJ com a dúvida.
O que pode ser???
[]‘s
JUNIOR
17. ago, 2007
Olá,
como ficam as definiçoes dos listeners qdo se usa persistence.xml e nao hibernate.cfg.xml?
Obrigado,
Davi
11. fev, 2008
Estou com a mesma dúvida do Júnior. Como faço para configurar no persistence.xml e não no hibernate.cfg.xml?
Antonio Lazaro
29. mai, 2008
Excelente artigo!!!
Parabéns.
Agora tenho uma dúvida.
Para avisar ao hibernate que comece a trabalahr e salvar meus índices preciso fazer algo?Ou só o save,update,delete bastam para isso?
Ele cria arquivos físicos de indice no diretório que settei?
Porque não consegui ver nenhum arquivo na pasta após uma operação de save.
Antonio Lazaro
29. mai, 2008
Outra coisa,
não consegui configurar essas propriedades na minha sessionfactory.
Estou utilizando spring, alguma idéia do que possa ser?
org.hibernate.search.store.FSDirectoryProvider
C:\desenvolvimento\xml
Jean Pierobom
07. out, 2009
O link para download do exemplo está quebrado. Você o possui ainda?
alvino
02. dez, 2009
tô vendo aqui. sobre o hibernate search e gostaria de saber como faço para cria o index automático.
Alvino Figueiredo
20. abr, 2010
Estou estudando o hibernate search para acrescentar em projeto trabalhando junto com ocr, criei algumas tabelas as principais documento e documento_ocr. Blz fiz a configuracao do hibernate.cfg.xml acrescentando as tag de eventos, mandei indexa o campo texto de documento_ocr, e fiz updates com nome de documento, e pecebi que buscava o nome do documento atualizado, ai mudei o update para fazer no campo texto da tabela documento_ocr, ai sim ao fazer a pesquisa percebi que ele nao acha dentro do arquivo index. So consigo pesquisa a frase atualizada de de mandar indexa. Ha isto tudo usando o “Search.getFullTextSession(session);” como sessao de conexao com banco. Como fazo para ativa os event declarado no cfg.xml sera ativado de forma automatica.
Fernando Z
21. mai, 2012
Ótimo post!
Estou usando lucene index para indexar dados de produtos, até aí tudo bem.
Mando reindexar os dados toda vez que a aplicação inicia e tbem, a cada alteração desses dados no banco (delete, insert, update)
O problema é quando defino um maxPoolSize de tamanho inferior a 25. A aplicação trava, me parece que há um deadlock na indexação… isso é estranho, pois de vez em quando ele para em 50% ou 10% ou 90%.
configuração do pool no applicationContext:
assim vai:
assim não vai:
se alguém puder ajudar..
Obrigado
Fábio Brandão
18. set, 2012
Pessoal, sei q esse post é bem antigo mas vou postar uma dúvida minha por aqui fiz uma implementação com o Hibernate Search e tudo está funcionando mapeamento pesquisa e tudo mais, agora eu gostaria de melhorar minha pesquisa pois quando ele faço a pesquisa por “sistema” e possui a palavra “sistemas” ele não encontra o registro eu gostaria que a minha pesquisa do Hibernate Search fizesse como o ilike do Criteria.
Criei um fórum no GUJ sobre a dúvida:
http://www.guj.com.br/java/282567-hibernate-search#1491089
Fabio
09. nov, 2012
Fabio, estou querendo integrar o hibernate com lucene e spring. vc conseguiu fazer?? tem como me dar um help??
obrigado