Published by

Il y a 15 ans -

Temps de lecture 10 minutes

Hands on Hibernate Search : Recherche full-text

Comme nous l’avions vu dans notre précédent billet (« Introduction à Hibernate Search (Googling your Persistent Domain Model)« ), Hibernate Search vise à réconcilier la recherche full-text et les modèles de persistance relationnels.
Pour ce faire, Hibernate Search se base sur Apache Lucene, un moteur d’indexation et de recherche full-text standalone très puissant et permet ainsi d’utiliser ses capacités dans le cadre d’une couche de mapping Hibernate.

Ce billet présente, au travers d’un exemple simple, les capacités de recherche full-text d’Hibernate Search.
L’exemple proposé permet l’indexation et la recherche de documents auxquels sont attachés des auteurs.
Vous retrouverez l’ensemble des sources présentées dans ce billet dans le repository SVN de Xebia France.

Structuration du projet et démarrage

Le projet utilise maven. Une fois les sources récupérées depuis notre dépot SVN, vous pouvez donc générer une configuration Eclipse à l’aide de la commande :

mvn eclipse:eclipse

Le projet déclare des dépendances vers :

  • hibernate-search en version 3.0.0.ga.
  • hibernate-annotations en version 3.3.0.ga.
  • hibernate-entitymanager en version 3.3.1.ga.
  • lucene-analyzers en version 2.3.1.
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-search</artifactId>
	<version>3.0.0.ga</version>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-annotations</artifactId>
	<version>3.3.0.ga</version>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>3.3.1.ga</version>
</dependency>
<dependency>
	<groupId>org.apache.lucene</groupId>
	<artifactId>lucene-analyzers</artifactId>
	<version>2.3.1</version>
</dependency>

Le projet est structuré sur l’arborescence classique maven :

  • src\main\java contient les sources du projet :
       – Le package fr.xebia.demo.hibernate.search.model contient les entités persistées qui seront mappées dans un index Lucene.
       – Le package fr.xebia.demo.hibernate.search.analysis contient un analyseur de langue anglaise.
  • src\main\test contient les sources des tests. Les tests « unitaires » ont ici été détournés pour faire la démonstration des capacités d’indexation et de recherche full-text d’Hibernate Search.

Mapping des entités sur l’index

Le mapping des entités dans un index Lucene se fait par annotation :

  • L’annotation @Indexed déclare une classe comme indexable
  • L’annotation @Analyzer définit l’implémentation d’analyseur syntaxique à utiliser pour cette indexation :
@Entity
@Indexed
@Analyzer(impl = SimpleEnglishAnalyzer.class)
@Table(name = DOCUMENT_TABLE_NAME)
public class Document {
  • L’annotation @DocumentId indique l’attribut à utiliser en tant qu’identifiant. Cet identifiant est utilisé par Hibernate Search afin d’assurer l’unicité des entités dans l’index :
@Id
@DocumentId
@Column(name = "id", nullable = false)
public Long getId() {
	return this.id;
}
  • L’annotation @Field marque un attribut comme devant être indexé. La propriété index = Index.TOKENIZED indique que le texte va être « tokenisé » via l’analyseur définit au niveau de la classe. La propriété store = Store.NO indique que le texte de l’attribut ne sera pas stocké dans l’index.
@Column(name = "title")
@Field(index = Index.TOKENIZED, store = Store.NO)
public String getTitle() {
	return title;
}

@Column(name = "summary")
@Field(index = Index.TOKENIZED, store = Store.NO)
public String getSummary() {
	return summary;
}
  • Dans le cadre d’une association, l’annotation @IndexedEmbedded indique que l’objet associé doit être indexé dans l’index de l’entité racine. Dans notre exemple, cette méthode permet d’effectuer une recherche full-text sur les entités de type Document basée sur les propriétés de son attribut author :
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE,
		CascadeType.REFRESH }, fetch = FetchType.EAGER)
@JoinColumn(name = "author_id")
@Fetch(FetchMode.JOIN)
@ForeignKey(name = "fk_document_author_id")
@IndexedEmbedded
public Author getAuthor() {
	return author;
}

Analyseur et tokeniseur

La « tokenisation » consiste à découper un texte en mots. Ce découpage se fait en suivant les règles définies dans un analyseur. Hibernate Search propose par défaut l’utilisation de org.apache.lucene.analysis.standard.StandardAnalyzer.
Il est possible de définir son propre analyseur en étendant la classe abstraite Analyzer dont il faut implémenter la méthode

public abstract TokenStream tokenStream(String fieldName, Reader reader);

C’est ce que propose la classe SimpleEnglishAnalyzer :

@Override
public final TokenStream tokenStream(String fieldName, Reader reader) {

	if (fieldName == null)
		throw new IllegalArgumentException("fieldName must not be null");
	if (reader == null)
		throw new IllegalArgumentException("reader must not be null");

	TokenStream result = new StandardTokenizer(reader);
	result = new StandardFilter(result);
	result = new LowerCaseFilter(result);
	result = new StopFilter(result, stopTable);
	result = new PorterStemFilter(result);
	return result;
}

Cette implémentation passe les mots en minuscule, supprime les mots courants, et utilise le PorterStemFilter pour supprimer les terminaisons morphologiques communes.

La configuration d’Hibernate Search est câblée sur le système d’écoute d’événements. Elle est donc transparente avec Hibernate Annotations.
Dans le cadre de notre exemple, la configuration Hibernate est réalisée dans le set up de la classe HibernateSearchDemoTestCase. La seule propriété de configuration que nous spécifions pour Hibernate Search est le répertoire de stockage des index :

@Override
protected void setUp() throws Exception {
	AnnotationConfiguration cfg = new AnnotationConfiguration();
	[...]
	cfg.setProperty("hibernate.search.default.indexBase", "target");
	[...]
}

Création et indexation des données de test

La création des données de test se fait dans la méthode populateDB de la classe de test (HibernateSearchDemoTestCase). L’indexation des données de test est faite de manière transparente au moment du commit de la transaction :

Transaction tx = this.testSession.beginTransaction();

Author bTate = new Author(
		"Bruce",
		"Tate",
		"Bruce A. Tate is a [...]");

[...]

Document beyondJava = new Document();
beyondJava.setTitle("Beyond Java");
beyondJava
		.setSummary("In Beyond Java, [...]");
beyondJava.setAuthor(bTate);
this.testSession.persist(beyondJava);

[...]

tx.commit();

Recherches full-text sur les entités

La recherche full-text de documents dans l’index se fait au travers de la méthode searchDocuments de la classe de test (HibernateSearchDemoTestCase) :

private List<Document> searchDocuments(String queryString) {
	FullTextSession searchSession = Search
			.createFullTextSession(this.testSession);
	Transaction tx = searchSession.beginTransaction();
	MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] {
			"title", "summary", "author.lastName", "author.firstName",
			"author.resume" }, new SimpleEnglishAnalyzer());
	FullTextQuery query;
	try {
		query = searchSession.createFullTextQuery(
				parser.parse(queryString), Document.class);
	} catch (ParseException pe) {
		logger.error("Error while parsing query.", pe);
		return new ArrayList<Document>(0);
	}
	List<Document> result = query.list();
	tx.commit();
	return result;
}

Pour effectuer une recherche full-text, il faut :

  • Créer une FullTextSession à partir de la session Hibernate.
  • Créer un parseur (MultiFieldQueryParser) en lui indiquant les champs sur lesquels la recherche doit avoir lieu et l’analyseur à utiliser pour tokeniser les chaînes de recherche (il est bien évidemment préférable d’utiliser le même que pour l’indexation des documents).
  • Créer une requête de recherche full-text à partir de la chaîne de recherche tokenisée.
  • Exécuter la méthode list sur la requête.

Hibernate Search retourne alors une liste d’entités « managées ».

Les cas de test

La démonstration peut être lancée via maven avec la commande :

mvn test

Les cas de test proposés sont :

  • Recherches simples ("java", "father", "JAVA"):
=====================================================
Simple Test (query == "java"):

Results list:
Document 1: Beyond Java:
   > Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   > Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 4: AspectJ Cookbook:
   > Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   > Author: Russell Miles (3)
             > Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================

=====================================================
Simple Test (query == "father"):

Results list:
Document 1: Beyond Java:
   > Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   > Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
=====================================================

=====================================================
Simple Case Test (query == "JAVA"):

Results list:
Document 1: Beyond Java:
   > Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   > Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 4: AspectJ Cookbook:
   > Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   > Author: Russell Miles (3)
             > Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================
  • Recherche par approximation ("jav~"):
=====================================================
Approximation Test (query == "jav~"):

Results list:
Document 1: Beyond Java:
   > Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   > Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   > Author: Bruce Tate (1)
             > Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 4: AspectJ Cookbook:
   > Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   > Author: Russell Miles (3)
             > Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================
  • Recherche par proximité (""engineer UK"~5"):
=====================================================
Proximity Test (query == ""engineer UK"~5"):

Results list:
Document 4: AspectJ Cookbook:
   > Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   > Author: Russell Miles (3)
             > Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================

Published by

Commentaire

4 réponses pour " Hands on Hibernate Search : Recherche full-text "

  1. Published by , Il y a 14 ans

    Très interessant comme sujet mais il n’y a pas les sources sous :
    « Vous retrouverez l’ensemble des sources présentées dans ce billet dans le repository SVN de Xebia France. »

    y a t’il un moyen d’obtenir votre exemple ?

    Merci.

  2. Published by , Il y a 13 ans

    Bonjour,

    Merci pour cet exemple très illustratif.

    A noter que dans la repository http://repository.jboss.com/maven2/, la version hibernate search est 3.0.0.GA et non 3.0.0.ga

    Cela peut poser problème pour ceux qui comme moi utilisent un proxy Archiva sous Linux…

    David.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.