如何开始使用Hibernate Search

如今,许多人正在使用Spring Boot开发企业Java应用程序。 在项目进行过程中,经常会出现创建复杂程度各异的搜索引擎的任务。 例如,如果您正在开发一个存储有关用户和书籍数据的系统,则迟早可能需要按用户名/姓氏,按书名/注释进行搜索。



在本文中,我将简要讨论可以帮助解决此类问题的工具。 然后,我将展示一个搜索服务的演示项目,其中实现了一个更有趣和更复杂的功能-实体,数据库和搜索索引的同步。 使用该演示项目的示例,您可以熟悉Hibernate Search,这是一种与全文索引Solr,Lucene,ElasticSearch进行通信的便捷方法。

在用于部署搜索引擎的工具中,我将选出三个。

Lucene是一个Java库,提供了低级的全文本搜索非规范化数据库接口。 在它的帮助下,您可以创建索引并用记录(文档)填充它们。 在此处阅读有关Lucene的更多信息。

Solr是最终的基于Lucene的软件产品,全文数据库,独立的独立Web服务器。 它具有用于索引和全文查询的http界面,它使您可以为文档建立索引并进行搜索。 Solr具有简单的API和内置的UI,从而无需手动操作索引。 在哈布雷(Habré)上, Solr和Lucene进行了比较好的分析

ElasticSearch是Solr的更现代的类似物。 它也基于Apache Lucene。 与Solr相比,ElasticSearch在为文档建立索引时可以承受更高的负载,因此可以用来为日志文件建立索引。 在网上可以找到比较Solr和ElasticSearch的详细

当然,这并不是一个完整的列表;上面,我只选择了最值得关注的系统。 有许多用于组织搜索的系统。 PostgreSQL具有全文搜索功能; 不要忘记狮身人面像。

主要问题


我们传递到主要的东西。 为了可靠/一致地存储数据,通常使用RDB(关系数据库)。 它根据ACID原则提供事务性。 为了使搜索引擎正常工作,使用索引来添加实体以及将通过其执行搜索的表中的那些字段。 也就是说,当新对象进入系统时,必须将其同时保存在关系数据库和全文索引中。

如果未在应用程序中组织此类更改的事务性质,则可能会发生各种不同步。 例如,您从数据库中选择,但是该对象不在索引中。 反之亦然:索引中有一个对象记录,并且该记录已从RDB中删除。

有几种方法可以解决此问题。 您可以使用JTA和Spring事务管理机制来手动组织事务更改。 或者,您可以采用更有趣的方式-使用Hibernate Search,它会自己完成所有操作。 默认情况下,使用Lucene,它将索引数据存储在文件系统内部,通常情况下,已配置到索引的连接。 当系统启动时,启动同步方法startAndWait(),并且在系统运行时,记录将存储在RDB和索引中。

为了说明该解决方案,我使用Hibernate Search准备了一个演示项目。 我们将创建一个包含用于读取,更新和搜索用户的方法的服务。 它可以构成内部数据库的基础,并可以通过名字,姓氏或其他元数据进行全文搜索。 要与关系数据库进行交互,我们使用Spring Data Jpa框架

让我们从表示用户的实体类开始:

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) 

一切都是标准的,我们用带有弹簧数据的所有必要注释的实体来表示。 使用实体,我们指示实体,使用表,我们指示数据库中的标签。 带索引的注释表示实体是可索引的,并且将落入全文索引。

对数据库中的用户进行CRUD操作所需的JPA存储库:

 internal interface UserRepository: JpaRepository<User, Long> 

与用户一起使用的服务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直接从数据库获取所有用户。 搜索使用userSearch组件从索引中检索用户。 使用用户搜索索引的组件:

 @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()   } } 

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)   } } 

我们使用两种方法从数据库中提取并按字符串搜索。

在应用程序运行之前,有必要初始化索引,我们使用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()) } } } 

对于测试,我们使用了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 

最后, 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') } 

该演示是使用Hibernate Search技术的简单示例,通过该示例,您可以了解如何结交Apache Lucene和Spring Data Jpa。 如有必要,可以将基于此演示的项目连接到Apache Solr或ElasticSearch。 该项目的潜在方向是搜索大索引(> 10 GB)并评估其中的性能。 您可以通过更深入地探索Hibernate Search的可能性来为ElasticSearch创建配置或更复杂的索引配置。

有用的链接:

Source: https://habr.com/ru/post/zh-CN428578/


All Articles