samedi 7 novembre 2009

Hibernate Search : Introduction

Les fonctions de recherche de données sont omniprésentes dans les applications de gestion.
Dans notre culture informatique, nous utilisons le plus souvent des reqûetes SQL (avec ou sans outil de mapping) pour répondre à ce genre de besoins. Mais lorsque l'on désire obtenir des recherches aussi flexibles que des requêtes "à la Google", cette solution n'est pas forcément la plus adaptée. Pourtant les bases de données relationnelles sont bien utiles pour stocker les données d'applications de gestion classiques.
Nous allons voir ensemble qu'Hibernate Search apporte une solution très intéressante à ce problème. Basé sur Lucene, ce framework permet de bénéficier de recherches avec tolérence aux fautes ou de recherches sémantiques en se basant sur des dictionnaires de synonymes, le tout étant lié au modèle d'entités JPA de l'application.
Il est même possible de l'utiliser pour effectuer des recherche par approximation phonétique!
Au lieu de rechercher dans la base de données, le framework cherche dans des objets appelés documents Lucene. Hibernate Search enregistre les entrées de la base à chaque insertion, modification ou suppression d'entités Hibernate dans ces documents Lucene afin de maintenir ses indexes à jour.

Deux modes de fonctionnement sont possibles :
  • le mode synchrone : mise a jour de l'index à chaque commit de transaction
  • le mode asynchrone : deux indexes, un index de lecture pour les recherches et un index d'écriture pour les mises a jour lors des commit, copie de l'index d'écriture sur l'index de lecture à intervalle régulier
Le deuxième procédé a l'avantage de ne pas impacter les performances des recherches à chaque transaction avec une mise à jour de l'index.
Cependant il a l'inconvénient de ne pas proposer des résultats de recherche aussi frais et à jour que le premier procédé. Une mise à jour immédiate de l' index de lecture à la demande est cependant possible.
Voyons un peu de code :
pour demander à Lucene d'indéxer une entité, on lui rajoute l'annotation @Indexed
@Entity
@Indexed
public class Appartement{

@ManyToOne
private Proprietaire proprietaire;

@Field(index=Index.TOKENIZED)
private String adresse;


}
@Field signifie que le champ sera indexé, Tokinzed implique que le champ doit être analysé syntaxiquement en utilisant un découpage par mots. Le champ description de notre classe Appartement peut en effet contenir plusieurs mots.

Pour faire une requête, on dispose d'un objet de type FullTextEntityManager auquel il est possible de passer des requêtes Lucene.

String[] fields = new String[]{"adresse", "proprietaire.nom"};
//Définition de l'analyseur
MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
//Définition du critère de recherche
org.apache.lucene.search.Query query = parser.parse("Victor");
javax.persistence.Query persistenceQuery = fullTextEntityManager.createFullTextQuery(query, Appartement.class);
//récupération des résultats
List<Appartement> appartements = persistenceQuery.getResultList();

Le code ci dessus permet de récupérer une liste d'appartements en basant la recherche sur l'adresse, le nom du propriétaire, ainsi que sur le mot clé "Victor".
Les appartements récupérés seront par exemple ceux qui se situent dans la rue Victor Hugo ou qui ont pour propriétaire Victor Dupont.
Cette requête étant tolérante aux fautes, ces résultats apparaitront aussi si vous tapez par exemple "Cictor" au lieu de "Victor"!
A noter que Lucene n'effectuera cette recherche par approximation que si la recherche exacte ne renvoie aucun résultat.

L'analyseur par défaut StandardAnalyzer est utilisé dans cet exemple. Il est toutefois possible de créer son propre analyseur pour ajouter des synonymes, ignorer certains mots communs (le, de...) , définir la sensibilité à la casse, le nombre de caractères éronnés autorisés pour la tolérance aux fautes de frappe ou encore des règles de recherche phonétique... Nous verrons ces fonctionnalités dans la dernière partie de ce tutoriel.

Et voilà, nous avons terminé cette première partie, la prochaine fois nous verrons comment intégrer des fonctions Hibernate Search dans une application de gestion d'agence immobilière (ultra simplifiée bien sûr) créée à l'aide du CRUD Generator de NetBeans.

Et vous dans vos applications, avez vous ressenti les limitations des recherches SQL? Pensez vous qu'une solution basée sur Lucene puisse améliorer la situation?

Lire la suite