この投稿では、このような問題の解決に役立つツールについて簡単に説明します。 次に、検索サービスのデモプロジェクトを紹介します。このプロジェクトでは、より興味深い複雑な機能(エンティティ、データベース、検索インデックスの同期)が実装されています。 このデモプロジェクトの例を使用すると、Hibernate Searchを知ることができます。HibernateSearchは、Solr、Lucene、ElasticSearchの全文インデックスと通信する便利な方法です。
検索エンジンを展開するためのツールのうち、3つを選びます。
Luceneは、低レベルの全文検索非正規化データベースインターフェイスを提供するJavaライブラリです。 その助けを借りて、インデックスを作成し、それらをレコード(ドキュメント)で埋めることができます。 Luceneの詳細については、 こちらをご覧ください 。
Solrは、Luceneベースの究極のソフトウェア製品であり、フルテキストデータベースであり、スタンドアロンの独立したWebサーバーです。 インデックス作成とフルテキストクエリ用のhttpインターフェイスがあり、ドキュメントのインデックス作成と検索が可能です。 Solrには、単純なAPIと組み込みのUIがあり、インデックスを手動で操作する必要がありません。 SolrとLuceneの優れた比較分析が Habréで明らかになりました。
ElasticSearchは、Solrのより現代的な類似物です。 また、Apache Luceneに基づいています。 Solrと比較して、ElasticSearchはドキュメントのインデックス作成時に高い負荷に耐えることができるため、ログファイルのインデックス作成に使用できます。 ネット上で、SolrとElasticSearchを比較した詳細な表を見つけることができます。
もちろん、これは完全なリストではありません;上記では、最も注目に値するシステムのみを選択しました。 検索を整理するためのシステムはたくさんあります。 PostgreSQLには全文検索機能があります。 スフィンクスを忘れないでください。
主な問題
主なものに移ります。 信頼性と一貫性のあるデータストレージのために、通常はRDB(リレーショナルデータベース)が使用されます。 ACIDの原則に従ってトランザクション性を提供します。 検索エンジンが機能するには、インデックスが使用されます。インデックスには、検索を実行するエンティティとテーブルのフィールドを追加する必要があります。 つまり、新しいオブジェクトがシステムに入ると、リレーショナルデータベースとフルテキストインデックスの両方に保存する必要があります。
そのような変更のトランザクションの性質がアプリケーション内で編成されていない場合、さまざまな種類の非同期化が発生する可能性があります。 たとえば、データベースから選択しますが、このオブジェクトはインデックスにありません。 またはその逆:インデックスにオブジェクトレコードがあり、RDBから削除されました。
この問題を解決するにはいくつかの方法があります。 JTAおよびSpring Transaction Managementメカニズムを使用して、トランザクションの変更を手動で整理できます。 または、より興味深い方法で行くことができます-Hibernate Searchを使用します。HibernateSearchはそれ自体をすべて実行します。 デフォルトでは、ファイルシステム内にインデックスデータを保存する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) } }
2つの方法を使用して、データベースから抽出し、文字列で検索します。
アプリケーションを実行する前に、インデックスを初期化する必要があります。これは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の構成またはより複雑なインデックス構成を作成できます。
便利なリンク: