Comment démarrer avec Hibernate Search

Aujourd'hui, beaucoup développent des applications Java d'entreprise à l'aide de Spring Boot. Au cours des projets, des tâches se posent souvent pour créer des moteurs de recherche de complexité variable. Par exemple, si vous développez un système qui stocke des données sur les utilisateurs et les livres, tôt ou tard, cela peut nécessiter une recherche par nom d'utilisateur / nom de famille, par nom / annotation pour les livres.



Dans cet article, je parlerai brièvement des outils qui peuvent aider à résoudre de tels problèmes. Et puis je présenterai un projet de démonstration d'un service de recherche, où une fonctionnalité plus intéressante et complexe est implémentée - synchronisation des entités, des bases de données et de l'index de recherche. En utilisant l'exemple de ce projet de démonstration, vous pouvez vous familiariser avec Hibernate Search, un moyen pratique de communiquer avec les index de texte intégral Solr, Lucene, ElasticSearch.

Parmi les outils de déploiement des moteurs de recherche, j'en citerais trois.

Lucene est une bibliothèque java qui fournit une interface de base de données dénormalisée de recherche en texte intégral de bas niveau. Avec son aide, vous pouvez créer des index et les remplir avec des enregistrements (documents). En savoir plus sur Lucene ici .

Solr est l'ultime produit logiciel basé sur Lucene, une base de données en texte intégral, un serveur Web séparé autonome. Il possède une interface http pour l'indexation et les requêtes de texte intégral, il vous permet d'indexer des documents et de les rechercher. Solr possède une API simple et une interface utilisateur intégrée, ce qui élimine le besoin de manipulation manuelle des index. Sur Habré, il y a eu une bonne analyse comparative de Solr et Lucene.

ElasticSearch est un analogue plus moderne de Solr. Il est également basé sur Apache Lucene. Par rapport à Solr, ElasticSearch peut supporter des charges plus élevées lors de l'indexation de documents et peut donc être utilisé pour indexer des fichiers journaux. Sur le net, vous pouvez trouver un tableau détaillé comparant Solr et ElasticSearch.

Bien sûr, ce n'est pas une liste complète; ci-dessus, je n'ai choisi que les systèmes qui méritent le plus d'attention. Il existe de nombreux systèmes pour organiser une recherche. PostgreSQL a des capacités de recherche en texte intégral; N'oubliez pas Sphinx.

Problème principal


Nous passons à l'essentiel. Pour un stockage de données fiable / cohérent, RDB (base de données relationnelle) est généralement utilisé. Il fournit la transactionnalité conformément aux principes de l'ACID. Pour que le moteur de recherche fonctionne, un index est utilisé pour ajouter des entités et les champs des tables par lesquels la recherche sera effectuée. En d'autres termes, lorsqu'un nouvel objet entre dans le système, il doit être enregistré à la fois dans la base de données relationnelle et dans l'index de texte intégral.

Si la nature transactionnelle de ces modifications n'est pas organisée dans votre application, différents types de désynchronisation peuvent se produire. Par exemple, vous sélectionnez dans une base de données, mais cet objet n'est pas dans l'index. Ou vice versa: il y a un enregistrement d'objet dans l'index, et il a été supprimé de RDB.

Il existe plusieurs façons de résoudre ce problème. Vous pouvez organiser manuellement les modifications transactionnelles à l'aide des mécanismes JTA et Spring Transaction Management . Ou vous pouvez opter pour une méthode plus intéressante - utilisez Hibernate Search, qui le fera tout seul. Par défaut, Lucene est utilisé, qui stocke les données d'index dans le système de fichiers, en général, la connexion à l'index est configurée. Lorsque le système démarre, vous démarrez la méthode de synchronisation startAndWait (), et pendant que le système est en cours d'exécution, les enregistrements seront stockés dans le RDB et l'index.

Pour illustrer cette solution, j'ai préparé un projet de démonstration avec Hibernate Search. Nous allons créer un service contenant des méthodes de lecture, de mise à jour et de recherche d'utilisateurs. Il peut constituer la base d'une base de données interne avec possibilité de recherche plein texte par prénom, nom ou autres métadonnées. Pour interagir avec les bases de données relationnelles, nous utilisons le framework Spring Data Jpa .

Commençons par la classe d'entité pour représenter l'utilisateur:

import org.hibernate.search.annotations.Field import org.hibernate.search.annotations.Indexed import javax.persistence.Entity import javax.persistence.Id import javax.persistence.Table @Entity @Table(name = "users") @Indexed internal data class User(        @Id        val id: Long,        @Field        val name: String,        @Field        val surname: String,        @Field        val phoneNumber: String) 

Tout est standard, nous désignons l'entité avec toutes les annotations nécessaires pour les données de printemps. En utilisant Entity, nous indiquons l'entité, en utilisant Table, nous indiquons l'étiquette dans la base de données. L' annotation indexée indique qu'une entité est indexable et tombera dans un index de texte intégral.

Référentiel JPA requis pour les opérations CRUD sur les utilisateurs de la base de données:

 internal interface UserRepository: JpaRepository<User, Long> 

Service de collaboration avec les utilisateurs, UserService.java:

 import org.springframework.stereotype.Service import javax.transaction.Transactional @Service @Transactional internal class UserService(private val userRepository: UserRepository, private val userSearch: UserSearch) {   fun findAll(): List<User> {       return userRepository.findAll()   }   fun search(text: String): List<User> {       return userSearch.searchUsers(text)   }   fun saveUser(user: User): User {       return userRepository.save(user)   } } 

FindAll obtient tous les utilisateurs directement de la base de données. La recherche utilise le composant userSearch pour extraire les utilisateurs de l'index. Composant pour travailler avec l'index de recherche d'utilisateurs:

 @Repository @Transactional internal class UserSearch(@PersistenceContext val entityManager: EntityManager) {   fun searchUsers(text: String): List<User> {       // fullTextEntityManager,  entityManager       val fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(entityManager)       //     Hibernate Search query DSL       val queryBuilder = fullTextEntityManager.searchFactory               .buildQueryBuilder().forEntity(User::class.java).get()       // ,            val query = queryBuilder               .keyword()               .onFields("name")               .matching(text)               .createQuery()       // Lucene Query  Hibernate Query object       val jpaQuery: FullTextQuery = fullTextEntityManager.createFullTextQuery(query, User::class.java)       //         return jpaQuery.resultList.map { result -> result as User }.toList()   } } 

Contrôleur REST, UserController.java:

 import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController import java.util.* @RestController internal class UserController(private val userService: UserService) {   @GetMapping("/users")   fun getAll(): List<User> {       return userService.findAll()   }   @GetMapping("/users/search")   fun search(text: String): List<User> {       return userService.search(text)   }   @PostMapping("/users")   fun insertUser(@RequestBody user: User): User {       return userService.saveUser(user)   } } 

Nous utilisons deux méthodes pour extraire de la base de données et rechercher par chaîne.

Avant l'exécution de l'application, il est nécessaire d'initialiser l'index, nous le faisons en utilisant ApplicationListener.

 package ru.rti import org.hibernate.search.jpa.Search import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component import javax.persistence.EntityManager import javax.persistence.PersistenceContext import javax.transaction.Transactional @Component @Transactional class BuildSearchService( @PersistenceContext val entityManager: EntityManager) : ApplicationListener<ApplicationReadyEvent> { override fun onApplicationEvent(event: ApplicationReadyEvent?) { try { val fullTextEntityManager = Search.getFullTextEntityManager(entityManager) fullTextEntityManager.createIndexer().startAndWait() } catch (e: InterruptedException) { println("An error occurred trying to build the search index: " + e.toString()) } } } 

Pour le test, nous avons utilisé PostgreSQL:

 spring.datasource.url=jdbc:postgresql:users spring.datasource.username=postgres spring.datasource.password=postgres spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.name=users 

Enfin, build.gradle :

 buildscript {   ext.kotlin_version = '1.2.61'   ext.spring_boot_version = '1.5.15.RELEASE'   repositories {       jcenter()   }   dependencies {       classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"       classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"       classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"       classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"   } } apply plugin: 'kotlin' apply plugin: "kotlin-spring" apply plugin: "kotlin-jpa" apply plugin: 'org.springframework.boot' noArg {   invokeInitializers = true } jar {   baseName = 'gs-rest-service'   version = '0.1.0' } repositories {   jcenter() } dependencies {   compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"   compile 'org.springframework.boot:spring-boot-starter-web'   compile 'org.springframework.boot:spring-boot-starter-data-jpa'   compile group: 'postgresql', name: 'postgresql', version: '9.1-901.jdbc4'   compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.6.Final'   compile group: 'org.hibernate', name: 'hibernate-search-orm', version: '5.10.3.Final'   compile group: 'com.h2database', name: 'h2', version: '1.3.148'   testCompile('org.springframework.boot:spring-boot-starter-test') } 

Cette démo est un exemple simple d'utilisation de la technologie Hibernate Search, avec laquelle vous pouvez comprendre comment se faire des amis Apache Lucene et Spring Data Jpa. Si nécessaire, les projets basés sur cette démo peuvent être connectés à Apache Solr ou ElasticSearch. Une direction potentielle pour le projet est de rechercher de grands indices (> 10 Go) et de mesurer les performances dans ces derniers. Vous pouvez créer des configurations pour ElasticSearch ou des configurations d'index plus complexes en explorant les possibilités de Hibernate Search à un niveau plus profond.

Liens utiles:

Source: https://habr.com/ru/post/fr428578/


All Articles