jan 08
Hibernate Search Lucene Spring Maven
L’objectif de cet article est de vous permettre d’appréhender la mise en place d’une indexation avec le moteur Lucene en s’aidant des frameworks java du moment sur le marché à savoir Hibernate et Spring.
Le projet de test que vous allez réaliser est téléchargeable à la fin de cet article, ce projet un projet maven donc pour le build : mvn package .Maven s’occupera de télécharger les dépendances pour vous .
Hibernate Search vous permet de profiter du moteur d’indexation Lucene pour indexer les objets de votre domaine métiers.
Lucene est basé sur une technologie d’indexation des contenus textuels similaire à Google ou d’autres moteurs de recherche. D’ailleurs, un sous-projet de Lucene, Nutch, offre les fonctionnalités de Google et permet de faire de la recherche sur des millions de pages web.
Lucene offre des performances inégalées et est scalable quelle que soit la volumétrie en terme de contenu ou de nombre de recherches.
Spring quand à lui est le meta-framework du moment, il intègre les concepts comme AOP IOC MVC et fourni un nombre considérable d’helpers pour l’intégrations de technologies, nous l’utiliserons dans notre projet pour manager hibernate et les transactions
L’intégration d’hibernate search est assez aisé et ce fait par le biais des annotations et les events hibernate.
Les annotations permettent de définir les propriétés que vous voulez indexer ainsi que leur stratégies d’indexations.
Les events s’occupent pour vous de synchroniser les indexes Lucene avec vos Entités hibernate

voilà après ce rapide tour d’horizon, nous allons mettre en place hibernate search pour un projet de test
1 Mise en place de MAVEN
Maven s’occupe de gérer pour vous les dépendances de vos projet et bien d’autres chose mais ce sera l’objet d’un prochain article.
Création de votre pom.xml avec les dépendances nécéssaires :
<name>TutorielHibernateSearch</name>
<modelversion>4.0.0</modelversion>
<groupid>com.karlverger.tutoriel</groupid>
<artifactid>hibernatesearch</artifactid></project>
<packaging>war</packaging>
<version>1.0.0-SNAPSHOT</version>
<repositories>
<repository>
<id>repository.jboss.org</id>
<name>JBoss Maven Repository</name>
<url>http://repository.jboss.org/maven2</url>
<layout>default</layout>
</repository>
</repositories>
<build></build>
<plugins>
<plugin>
<artifactid>maven-compiler-plugin</artifactid>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
<dependencies>
<dependency>
<groupid>commons-dbcp</groupid>
<artifactid>commons-dbcp</artifactid>
<version>1.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>org.hibernate</groupid>
<artifactid>hibernate-annotations</artifactid>
<version>3.3.0.ga</version>
</dependency>
<dependency>
<groupid>org.hibernate</groupid>
<artifactid>hibernate</artifactid>
<version>3.2.5.ga</version>
</dependency>
<dependency>
<groupid>org.hibernate</groupid>
<artifactid>hibernate-search</artifactid>
<version>3.0.0.GA</version>
</dependency> </dependencies> <dependency>
<groupid>org.apache.lucene</groupid>
<artifactid>lucene-core</artifactid>
<version>2.2.0</version>
</dependency>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version>5.1.5</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring</artifactid>
<version>2.0.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-jdbc</artifactid>
<version>2.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-mock</artifactid>
<version>2.0.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-remoting</artifactid>
<version>2.0.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-support</artifactid>
<version>2.0.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.1</version>
</dependency>
2 Configuration de descripteur WEB
Création du fichier WEB-INF/web.xml contenant les filtres et listeners nécéssaires :
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param></context-param></web-app>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext*.xml
</param-value> <!–
filtre Spring permmettant
de mettre en place le pattern Session In View Hibernate
–>
<filter>
<filter-name>Hibernate Session In View Filter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>
<!–
permet d’appliquer le pattern
session in view d’hibernate a tous nos controler
–>
<filter-mapping>
<filter-name>Hibernate Session In View Filter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>web</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>web</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
</welcome-file-list>
voilà, arriver à ce point nous avons un projet web exploitable, il nous reste à configurer hibernate ainsi que le transactionManager et les eventListeners pour la synchronization entre votre domaine métiers et les indexes Lucene.
Pour ce faire nous allons créer le fichier WEB-INF/applicationContext-hibernate.xml
Le fichier est auto documenté vous devriez comprendre aisément chacun des noeuds déclarer
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</beans> xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
<!– ================= HIBERNATE ================= –>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost/test"></property>
<property name="username" value="root"></property>
<property name="password" value="Rootadel2003"></property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!–définition des beans hibernate–>
<property name="annotatedClasses">
<list>
<value>com.karlverger.tutoriel.hibernatesearch.entities.Item</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.cache.use_second_level_cache=false
hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
hibernate.show_sql=true
<!–
les properties nécéssaire à la mise en place d’hibernate-search
pour le stockage des index
–>
hibernate.search.default.directory_provider = org.hibernate.search.store.FSDirectoryProvider
hibernate.search.default.indexBase = /home/ubuntu/lucene/indexes2
</value>
</property>
<!–
déclaration des listeners hibernate search pour qu’il réalise l’indexation
en mode synchrones
–>
<property name="eventListeners">
<map>
<entry key="post-update">
<bean class="org.hibernate.search.event.FullTextIndexEventListener"></bean>
</entry>
<entry key="post-insert">
<bean class="org.hibernate.search.event.FullTextIndexEventListener"></bean>
</entry>
<entry key="post-delete">
<bean class="org.hibernate.search.event.FullTextIndexEventListener"></bean>
</entry>
</map>
</property>
</bean>
<!– enable the configuration of transactional behavior based on annotations –>
<tx:annotation-driven transaction-manager="transactionManager">
<!– a PlatformTransactionManager is still required –>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!– /================= HIBERNATE ================= –>
<!–
Beans DAO
–>
<bean id="itemDAO" class="com.karlverger.tutoriel.hibernatesearch.dao.ItemDAO">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
</tx:annotation-driven>
Maintenant nous allons créer notre POJO Item pour notre test ainsi qu’un DAO correspondant.
Notre POJO Item va se voir ajouter des annotations permettant sa persistance en base et son indexation par le moteur Lucene. Le bean est auto documenté.
package com.karlverger.tutoriel.hibernatesearch.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
/**
* permet de déclarer le POJO comme entity à persister
*/
/**
* permet de déclarer à hibernate search qu’il faut prendre en compte
* cette entité afin de l’indexer
*/
@Indexed
/**
* permet de déclarer la table correspondante à ce pojo
*/
@Table(name = "item")
public class Item implements Serializable {
/**
* Anotation définissant cette proprié comme PK
*/
@Id
/**
* Propriété spéciale d’hibernate search permettant d’assurer l’unicité
* de l’index pour une entité
*/
@DocumentId
/**
* Défini la stratégie de création de l’identifiant de la pk
*/
@GeneratedValue(strategy=GenerationType.AUTO)
/**
* défini de manière explicite la colone de base de donné mappé avec
* cette propriété
*/
@Column(name = "ITEM_ID", nullable = false)
private Long itemId;
@Column(name = "NOM")
/**
* permet de définir la colonne comme source pour les index lucene
* de l’entité, noté que tous les champs ne doivent pas etre
* TOKENIZED par example une date ne doit pas l’être ainsi que les
* propriété qui sont prévues pour effectuer des tris
*/
@Field(index=Index.TOKENIZED)
private String nom;
public Long getItemId() {
return itemId;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
}
Le fichier ItemDAO etend HibernateDaoSupport afin de bénéficier des facilité qu’apporte Spring pour la gestion de la session hibernate.
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class ItemDAO extends HibernateDaoSupport{
public void update(Item item){
getHibernateTemplate().update(item);
}
public void create(Item item){
getHibernateTemplate().persist(item);
}
/**
* effectue une recherche fullTexte Lucene sur les indexs
* défini dans le model
* Item, a savoir nom par rapport a la demande passé en parametre.
*
* Cette méthode permettra de tester l’indexation
*
* @param searchPattern
* @return
*/
public List luceneSearch(String searchPattern){
List result = null;
try{
FullTextSession fullTextSession = Search.createFullTextSession(getHibernateTemplate().getSessionFactory().getCurrentSession());
MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[]{"nom"}, new StandardAnalyzer());
org.apache.lucene.search.Query query = parser.parse(searchPattern);
org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery(query, Item.class);
result = hibQuery.list();
}catch (org.apache.lucene.queryParser.ParseException ex) {
Logger.getLogger(ItemDAO.class.getName()).log(Level.SEVERE, null, ex);
}
return result;
}
}
A présent nous pouvons effectuer un petit test unitaire pour valider notre mise en place
pour rappel, hibernate est configurer pour pointer sur la base test sur localhost avec
username : login
password : password
Nous allons créer la table nécéssaire
PRIMARY KEY(`ITEM_ID`)
)
ENGINE = InnoDB;
Maintenant nous pouvons créer notre test unitaire, celui ci s’appuie sur le framework spring afin de bénéficier de l’injection et du contexte transactionnel.
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
public class ItemDAOTest extends AbstractTransactionalDataSourceSpringContextTests {
protected ItemDAO itemDAO;
public ItemDAOTest() {
setPopulateProtectedVariables(true);
}
public void testCreate(){
Item item = new Item();
item.setNom("un nouvel item");
getItemDAO().create(item);
setComplete();//nécéssaire pour que le test ne soit pas rolbacké en base
}
public void testLuceneSearch(){
List<item> items = getItemDAO().luceneSearch("un");</item>
System.out.println("Les items suivants ont étés trouvés : ");
for (Item item : items) {
System.out.println(item.getNom());
}
}
public ItemDAO getItemDAO() {
return itemDAO;
}
public void setItemDAO(ItemDAO itemDAO) {
this.itemDAO = itemDAO;
}
protected String[] getConfigLocations() {
return new String[] {"applicationContext-hibernate.xml"};
}
}
ce test vous permet de créer un nouvel item en base, les listener hibernate search s’occuperont de l’indexer auprès du moteur Lucene, puis d’effectuer une recherche sur les indexes.
Voilà en espérant que ce tutoriel vous a été utile. Je rentrerais plus en détail sur les stratégies de mapping et d’indexation dans un prochain article.

mai 8th, 2008 at 18:52
Hi.
I have a little question in your model.
Your are using “Open Session In View” for hibernate. Ok, its really fine. But, when you are using hibernate search, this “open session” works fine?
And other question, in the method: “luceneSearch” you are creating a fullTextSession, but you never close this session. Is ok that?
Thanks,
Cristian Ventura.
juillet 23rd, 2008 at 14:25
Bon premier exemple
Merci !