如今,许多人正在使用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创建配置或更复杂的索引配置。
有用的链接: