Hibernate Search fournit la fonctionnalité de recherche de texte brut aux applications d'Hibernate. Cette fonctionnalité est particulièrement adaptée aux recherches de demandes pour lesquelles des solutions basées SQL ne sont pas adaptées, c'est à dire les recherches de texte brut, approximations, et géolocalisation. Hibernate Search utilise Apache Lucene comme moteur de recherche de texte intégral, mais est conçu pour minimiser la charge de maintenance. Une fois configuré, l'indexation, le clustering et la synchronisation de données sont maintenus en toute transparence, ce qui vous permet de mettre l'accent sur les besoins de votre entreprise.
Pour cette section, imaginez que vous ayez une base de données contenant des détails de livres. Votre application contient les classes gérées de Hibernate example.Book et example.Author et vous souhaitez ajouter des fonctionnalités de recherche de texte libre à votre application pour pouvoir rechercher des livres.
Exemple 13.1. Livre et auteur d'entités avant l'ajout d'annotations spécifiques de Hibernate Search
package example;
...
@Entity
public class Book {
@Id
@GeneratedValue
private Integer id;
private String title;
private String subtitle;
@ManyToMany
private Set<Author> authors = new HashSet<Author>();
private Date publicationDate;
public Book() {}
// standard getters/setters follow here
...
}
package example;
...
@Entity
public class Book {
@Id
@GeneratedValue
private Integer id;
private String title;
private String subtitle;
@ManyToMany
private Set<Author> authors = new HashSet<Author>();
private Date publicationDate;
public Book() {}
// standard getters/setters follow here
...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
package example;
...
@Entity
public class Author {
@Id
@GeneratedValue
private Integer id;
private String name;
public Author() {}
// standard getters/setters follow here
...
}
package example;
...
@Entity
public class Author {
@Id
@GeneratedValue
private Integer id;
private String name;
public Author() {}
// standard getters/setters follow here
...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Il vous faudra pour cela ajouter quelques annotations aux classes Book et Author. La première annotation @Indexed désigne la classe Book comme indexable. Hibernate Search stocke un ID sans token dans l'index pour s'assurer de l'unicité d'index d'une entité donnée. @DocumentId indique la propriété à utiliser et est similaire, dans la plupart des cas, à la clé primaire de la base de données. L'annotation @DocumentId est facultative dans le cas où une annotation @Id existe.
Ensuite, les champs que vous souhaitez rendre recherchables doivent être indiqués comme tel. Dans cet exemple, commencez par title et subtitle et annotez-les avec @Field. Le paramètre index=Index.YES s'assurera que le texte sera indexé tandis que analyze=Analyze.YES s'assure que le texte sera analysé en utilisant l'analyseur Lucene par défaut. L'analyse signifie généralement diviser une phrase en mots et exclure certains termes courants comme « a » (un/une) ou « the » (le/la). Nous abordons les analyseurs plus en détail par la suite. Le troisième paramètre que nous indiquons dans @Field, store=Store.NO, permet de garantir que les données ne seront pas stockées dans l'index. Le fait ou non que ces données soient stockées dans l'index n'aura aucun impact sur la possibilité de les rechercher. Du point de vue de Lucene, il n'est pas nécessaire de préserver les données une fois l'index créé. L'avantage de les stocker réside dans la capacité de les récupérer par projections ( voir Section 13.3.1.10.5, « Projection »).
Sans projections, Hibernate Search exécutera par défaut une requête Lucene afin de trouver les identifiants de la base de données des entités correspondant aux critères de requête et d'utiliser ces identifiants pour récupérer des objets gérés de la base de données. La décision d'utiliser une projection ou non devra être prise au cas par cas. Le comportement par défaut est recommandé puisqu'il renvoie des objets gérés tandis que les projections ne renvoient que des matrices d'objet.
Veuillez noter que index=Index.YES, analyze=Analyze.YES et store=Store.NO sont les valeurs par défaut de ces paramètres et peuvent être omises.
L'annotation @DateBridge n'a pas encore été abordée. Cette annotation fait partie des ponts de champ intégrés dans Hibernate Search. L'index Lucene se base uniquement sur des chaînes. C'est pour cette raison que Hibernate Search doit convertir les types de données des champs indexés en chaîne et vice-versa. Une gamme de ponts prédéfinis est fournie, y compris la classe DateBridge qui convertira une classe java.util.Date en une classe String avec la résolution indiquée. Pour plus de détails, veuillez consulter Section 13.2.4, « Ponts ».
Ce qui nous laisse avec @IndexedEmbedded. Cette annotation est utilisée pour indexer des entités associées (@ManyToMany, @*ToOne, @Embedded et @ElementCollection) comme faisant partie de l'entité propriétaire, ce qui est nécessaire puisqu'un document d'index Lucene est une structure de données plate ne connaissant rien aux relations d'objets. Pour faire en sorte que le nom des auteurs soit trouvable, vous devez vous assurer que les noms sont indexés comme faisant partie du livre. En plus de @IndexedEmbedded, vous devrez signaler tous les champs de l'entité associée que vous souhaitez inclure dans l'index comme @Indexed. Pour plus d'informations, veuillez consulter Section 13.2.1.3, « Objets associés et intégrés »
Hibernate Search indexe de manière transparente chaque entité persistante, mise à jour ou supprimée par Hibernate Core. Vous devez cependant créer un index Lucene initial pour les données déjà présentes dans votre base de données. Une fois les propriétés et annotations ci-dessus ajoutées, il est temps de déclencher un index de groupe initial pour vos livres. Pour y parvenir, utilisez l'un des snippets de code suivants (cf. Section 13.4.3, « Reconstruction de l'index ») :
Exemple 13.3. Indexation de données à l'aide d'une session Hibernate
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Exemple 13.4. Indexation de données à l'aide de JPA
EntityManager em = entityManagerFactory.createEntityManager();
FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
fullTextEntityManager.createIndexer().startAndWait();
EntityManager em = entityManagerFactory.createEntityManager();
FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
fullTextEntityManager.createIndexer().startAndWait();
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Après avoir exécuté le code ci-dessus, vous devriez pouvoir voir un index Lucene sous /var/lucene/indexes/example.Book. Inspectez cet index avec Luke. Cela vous aidera à comprendre comment Hibernate Search fonctionne.
Exemple 13.5. Utiliser une session Hibernate Search pour créer et exécuter une recherche
FullTextSession fullTextSession = Search.getFullTextSession(session);
Transaction tx = fullTextSession.beginTransaction();
// create native Lucene query using the query DSL
// alternatively you can write the Lucene query using the Lucene query parser
// or the Lucene programmatic API. The Hibernate Search DSL is recommended though
QueryBuilder qb = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity( Book.class ).get();
org.apache.lucene.search.Query query = qb
.keyword()
.onFields("title", "subtitle", "authors.name", "publicationDate")
.matching("Java rocks!")
.createQuery();
// wrap Lucene query in a org.hibernate.Query
org.hibernate.Query hibQuery =
fullTextSession.createFullTextQuery(query, Book.class);
// execute search
List result = hibQuery.list();
tx.commit();
session.close();
FullTextSession fullTextSession = Search.getFullTextSession(session);
Transaction tx = fullTextSession.beginTransaction();
// create native Lucene query using the query DSL
// alternatively you can write the Lucene query using the Lucene query parser
// or the Lucene programmatic API. The Hibernate Search DSL is recommended though
QueryBuilder qb = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity( Book.class ).get();
org.apache.lucene.search.Query query = qb
.keyword()
.onFields("title", "subtitle", "authors.name", "publicationDate")
.matching("Java rocks!")
.createQuery();
// wrap Lucene query in a org.hibernate.Query
org.hibernate.Query hibQuery =
fullTextSession.createFullTextQuery(query, Book.class);
// execute search
List result = hibQuery.list();
tx.commit();
session.close();
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Exemple 13.6. Utilisation de JPA pour créer et exécuter une recherche
EntityManager em = entityManagerFactory.createEntityManager();
FullTextEntityManager fullTextEntityManager =
org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
em.getTransaction().begin();
// create native Lucene query using the query DSL
// alternatively you can write the Lucene query using the Lucene query parser
// or the Lucene programmatic API. The Hibernate Search DSL is recommended though
QueryBuilder qb = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity( Book.class ).get();
org.apache.lucene.search.Query query = qb
.keyword()
.onFields("title", "subtitle", "authors.name", "publicationDate")
.matching("Java rocks!")
.createQuery();
// wrap Lucene query in a javax.persistence.Query
javax.persistence.Query persistenceQuery =
fullTextEntityManager.createFullTextQuery(query, Book.class);
// execute search
List result = persistenceQuery.getResultList();
em.getTransaction().commit();
em.close();
EntityManager em = entityManagerFactory.createEntityManager();
FullTextEntityManager fullTextEntityManager =
org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
em.getTransaction().begin();
// create native Lucene query using the query DSL
// alternatively you can write the Lucene query using the Lucene query parser
// or the Lucene programmatic API. The Hibernate Search DSL is recommended though
QueryBuilder qb = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity( Book.class ).get();
org.apache.lucene.search.Query query = qb
.keyword()
.onFields("title", "subtitle", "authors.name", "publicationDate")
.matching("Java rocks!")
.createQuery();
// wrap Lucene query in a javax.persistence.Query
javax.persistence.Query persistenceQuery =
fullTextEntityManager.createFullTextQuery(query, Book.class);
// execute search
List result = persistenceQuery.getResultList();
em.getTransaction().commit();
em.close();
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Nous partons du principe que le titre de l'entité d'un livre indexé est Refactoring: Improving the Design of Existing Code et que des clics sont nécessaires pour les requêtes suivantes : refactor, refactors, refactored et refactoring. Sélectionnez une classe d'analyseur dans Lucene qui applique l'analyse de radical d'un mot lors de l'indexation et de la recherche. Hibernate Search offre plusieurs façons de configurer l'analyseur (voir Section 13.2.3.1, « Analyseur par défaut et analyseur par classe » pour plus d'informations) :
Définir la propriété analyzer dans le fichier de configuration. La classe spécifiée devient l'analyseur par défaut.
Définissez l'annotation @Analyzer au niveau de l'entité.
Définissez l'annotation @Analyzer au niveau du champ.
Spécifiez le nom entier de la classe ou l'analyseur à utiliser, ou voir un analyseur défini par l'annotation @AnalyzerDef avec l'annotation @Analyzer. Le framework de l'analyseur Solr et ses fabriques sont utilisés dans la dernière option. Pour plus d'informations sur les classes de fabrique, voir la JavaDoc Solrf ou lire un section correspondante Wiki Solr (http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters)
Dans cet exemple, StandardTokenizerFactory est utilisé par deux fabriques de filtre : LowerCaseFilterFactory et SnowballPorterFilterFactory. Le générateur de jetons divise les mots à chaque caractère de ponctuation et tiret mais ne touche pas aux adresses e-mail et noms d'hôtes d'internet intacts. Le générateur de jetons est idéal pour cela mais également pour d'autres opérations d'ordre général. Le filtre de bas-de-casse convertit toutes les lettres dans le jeton en bas-de-casse et le filtre boule de neige applique une recherche de radical linguistique.
Si vous utilisez le cadre Solr, utilisez le générateur de jetons avec un nombre de filtres arbitraire.
Exemple 13.7. Utilisation de @AnalyzerDef et du cadre Solr pour définir et utiliser un analyseur
@Indexed
@AnalyzerDef(
name = "customanalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = SnowballPorterFilterFactory.class,
params = { @Parameter(name = "language", value = "English") })
})
public class Book implements Serializable {
@Field
@Analyzer(definition = "customanalyzer")
private String title;
@Field
@Analyzer(definition = "customanalyzer")
private String subtitle;
@IndexedEmbedded
private Set authors = new HashSet();
@Field(index = Index.YES, analyze = Analyze.NO, store = Store.YES)
@DateBridge(resolution = Resolution.DAY)
private Date publicationDate;
public Book() {
}
// standard getters/setters follow here
...
}
@Indexed
@AnalyzerDef(
name = "customanalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = SnowballPorterFilterFactory.class,
params = { @Parameter(name = "language", value = "English") })
})
public class Book implements Serializable {
@Field
@Analyzer(definition = "customanalyzer")
private String title;
@Field
@Analyzer(definition = "customanalyzer")
private String subtitle;
@IndexedEmbedded
private Set authors = new HashSet();
@Field(index = Index.YES, analyze = Analyze.NO, store = Store.YES)
@DateBridge(resolution = Resolution.DAY)
private Date publicationDate;
public Book() {
}
// standard getters/setters follow here
...
}
Copy to ClipboardCopied!Toggle word wrapToggle overflow
Utilisez @AnalyzerDef pour définir un analyseur, puis appliquez-le à des entités et propriétés à l'aide de @Analyzer. Dans cet exemple, le customanalyzer est défini mais pas appliqué à l'entité. L'analyseur est uniquement appliqué aux propriétés title et subtitle. Une définition d'analyseur est globale. Définissez l'analyseur pour une entité et réutilisez la définition pour d'autres entités si requis.
Nous aidons les utilisateurs de Red Hat à innover et à atteindre leurs objectifs grâce à nos produits et services avec un contenu auquel ils peuvent faire confiance. Découvrez nos récentes mises à jour.
Rendre l’open source plus inclusif
Red Hat s'engage à remplacer le langage problématique dans notre code, notre documentation et nos propriétés Web. Pour plus de détails, consultez le Blog Red Hat.
À propos de Red Hat
Nous proposons des solutions renforcées qui facilitent le travail des entreprises sur plusieurs plates-formes et environnements, du centre de données central à la périphérie du réseau.