jeudi 21 janvier 2010

Hibernate Search : intégration à une application existante (partie 2)

Partie 2 : Intégration d'Hibernate search dans une application existante.

Dans la partie précédente, nous avons vu les concepts d'Hibernate search et de Lucene.

Aujourd'hui nous allons voir comment intégrer Hibernate Search dans une application existante, que nous allons générer à l'aide du CRUD Generator de NetBeans 6.8. Pour mémoire, ce billet explique comment générer une application web avec cet outil.

Pour des raisons de compatibilité entre JPA et Hibernate search, nous allons cette fois générer une appli web JEE 5. La version d'Hibernate search que nous ajouterons au projet NetBeans est la version 3.1.1GA.
Cette application sera basée sur les technologies JSF et JPA.

Tout d'abord, on définit nos entités JPA, Proprietaire et Appartement :

@Entity
@Indexed
public class Appartement implements Serializable {

@Id
@DocumentId
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne
@IndexedEmbedded
private Proprietaire proprietaire;

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

//getters & setters
...
}


@Entity
public class Proprietaire implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

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

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

//getters & setters
...
}


Vous remarquerez que j'ai ajouté des annotations spécifiques à Hibernate search sur ces entités.
@DocumentId permet de dire au framework que nous allons indexer les objets de type Appartement dans Lucene.
@Field(index=Index.TOKENIZED) signifie que les champs en question seront ajoutés à l'index.

Même si ce sont les appartements qui nous intéressent dans le cadre de nos recherches, nous voulons indexer certaines informations du propriétaire. C'est pourquoi la classe liée Proprietaire (ce lien étant exprimé pour Hibernate search par l'annotation @IndexEmbedded) contient elle aussi des champs pourvus de l'annotation @Field.

Une fois notre application web générée à partir de nos entités, nous pouvons ajouter notre code spécifique qui nous permettra d'effectuer des recherches "full text" sur les appartements de notre agence.

Ajoutons la méthode suivante à la classe AppartementJPAController :

public List<Appartement> filterAppartements(String filter) {
EntityManager em = getEntityManager();
try {
//Définition des champs de recherche
String[] fields = new String[]{"adresse", "proprietaire.lastName"};
MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
org.apache.lucene.search.Query query = null;
try {
query = parser.parse(filter);
} catch (ParseException ex) {
Logger.getLogger(AppartementJpaController.class.getName()).log(Level.SEVERE, null, ex);
}
//création du fullTextEntityManager
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
javax.persistence.Query persistenceQuery = fullTextEntityManager.createFullTextQuery(query, Appartement.class).setCriteriaQuery(null);
//récupération des résultats
return persistenceQuery.getResultList();
} finally {
em.close();
}
}


Cette méthode permet de lancer une recherche sur les appartements à partir d'un filtre de recherche. Un objet "fullTextEntityManager" est créé à partir de l'entity manager JPA.
La recherche sera effectuée sur les champs représentant l'adresse de l'appartement ainsi que le nom et prénom de son propriétaire.
Pour reprendre l'exemple de l'article précédent, le mot clé "Victor" renverra aussi bien les appartements de la rue Victor Hugo que les appartements dont le propriétaire s'appele Victor.

Modifions maintenant notre controlleur JSF pour lui permettre d'appeler cette méthode.
On ajoute à la classe AppartementController une variable "filter" ainsi qu'une méthode pour lancer la recherche :

private String filter;

public String filter() {
if (filter!=null)
appartementItems = jpaController.filterAppartements(getFilter());
pagingInfo.setItemCount(appartementItems.size());
return "appartement_list";
}



Cette méthode permet de recharger la liste des appartements affichés par la jsp pour n'afficher que ceux qui correspondent à notre recherche. L'appel à la méthode pagingInfo.setItemCount() permet d'afficher une valeur correcte pour le nombre d'éléments à afficher au niveau de la pagination (par exemple "items 1 ... 5 of 10").

Enfin, nous allons modifier la JSP appartement/List.jsp pour ajouter le champ de recherche qui appelera la méthode filter() du controller JSF (AppartementController) :




Et voilà nous avons une application fonctionnelle qui nous permet de lancer des recherches avec Lucene.
Dans la prochaine partie nous verrons comment améliorer les résultats de ces recherches avec l'approximation et les synonymes.