Saat ini, banyak yang mengembangkan aplikasi Java enterprise menggunakan spring boot. Dalam perjalanan proyek, tugas sering muncul untuk membuat mesin pencari dengan kompleksitas yang berbeda-beda. Misalnya, jika Anda mengembangkan sistem yang menyimpan data tentang pengguna dan buku, maka cepat atau lambat mungkin memerlukan pencarian berdasarkan nama pengguna / nama keluarga, dengan nama / anotasi untuk buku.

Dalam posting ini, saya akan secara singkat berbicara tentang alat yang dapat membantu menyelesaikan masalah seperti itu. Dan kemudian saya akan menyajikan proyek demo layanan pencarian, di mana fitur yang lebih menarik dan kompleks diterapkan - sinkronisasi entitas, database, dan indeks pencarian. Dengan menggunakan contoh proyek demo ini, Anda dapat berkenalan dengan Pencarian Hibernate, cara mudah untuk berkomunikasi dengan indeks teks lengkap Solr, Lucene, ElasticSearch.
Di antara alat untuk menyebarkan mesin pencari, saya akan memilih tiga.
Lucene adalah perpustakaan java yang menyediakan antarmuka basis data denormalized pencarian teks lengkap tingkat rendah. Dengan bantuannya, Anda dapat membuat indeks dan mengisinya dengan catatan (dokumen). Baca lebih lanjut tentang Lucene di
sini .
Solr adalah produk perangkat lunak berbasis Lucene, database teks lengkap, server web terpisah yang berdiri sendiri. Ini memiliki antarmuka http untuk pengindeksan dan permintaan teks lengkap, memungkinkan Anda untuk mengindeks dokumen dan mencari mereka. Solr memiliki API sederhana dan UI bawaan, yang menghilangkan kebutuhan untuk manipulasi indeks secara manual. Pada Habrรฉ ada
analisis komparatif yang baik
dari Solr dan Lucene.
ElasticSearch adalah analog Solr yang lebih modern. Itu juga didasarkan pada Apache Lucene. Dibandingkan dengan Solr, ElasticSearch dapat menahan muatan yang lebih tinggi saat mengindeks dokumen dan karenanya dapat digunakan untuk mengindeks file log. Di internet Anda dapat menemukan
tabel terperinci yang membandingkan Solr dan ElasticSearch.
Ini, tentu saja, bukan daftar lengkap, di atas, saya hanya memilih sistem yang pantas paling diperhatikan. Ada banyak sistem untuk mengatur pencarian. PostgreSQL memiliki kemampuan pencarian teks lengkap; Jangan lupa tentang Sphinx.
Masalah utama
Kami lolos ke hal utama. Untuk penyimpanan data yang andal / konsisten, RDB (basis data relasional) biasanya digunakan. Ini memberikan transaksionalitas sesuai dengan prinsip-prinsip ACID. Agar mesin pencari berfungsi, indeks digunakan untuk menambahkan entitas dan bidang tabel yang digunakan untuk melakukan pencarian. Yaitu, ketika objek baru memasuki sistem, itu harus disimpan baik dalam database relasional dan dalam indeks teks lengkap.
Jika sifat transaksional dari perubahan tersebut tidak diatur dalam aplikasi Anda, maka berbagai macam desinkronisasi dapat terjadi. Misalnya, Anda memilih dari database, tetapi objek ini tidak ada dalam indeks. Atau sebaliknya: ada catatan objek dalam indeks, dan itu dihapus dari RDB.
Ada beberapa cara untuk mengatasi masalah ini. Anda dapat secara manual mengatur perubahan transaksional menggunakan mekanisme JTA dan
Spring Transaction Management . Atau Anda bisa menggunakan cara yang lebih menarik - gunakan Hibernate Search, yang akan melakukan semuanya dengan sendirinya. Secara default, Lucene digunakan, yang menyimpan data indeks di dalam sistem file, secara umum, koneksi ke indeks dikonfigurasi. Ketika sistem dimulai, Anda memulai metode sinkronisasi startAndWait (), dan saat sistem sedang berjalan, catatan akan disimpan dalam RDB dan indeks.
Untuk menggambarkan solusi ini, saya menyiapkan proyek demo dengan Pencarian Hibernate. Kami akan membuat layanan yang berisi metode untuk membaca, memperbarui, dan mencari pengguna. Ini dapat membentuk dasar dari basis data internal dengan kemungkinan pencarian teks lengkap dengan nama depan, nama belakang atau meta-data lainnya. Untuk berinteraksi dengan basis data relasional, kami menggunakan
kerangka kerja Spring Data Jpa .
Mari kita mulai dengan kelas entitas untuk mewakili pengguna:
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)
Semuanya standar, kami menunjukkan entitas dengan semua anotasi yang diperlukan untuk data pegas. Menggunakan
Entity, kami menunjukkan entitas, menggunakan
Tabel kami menunjukkan label dalam database. Anotasi yang
diindeks menunjukkan bahwa suatu entitas dapat diindeks dan akan jatuh ke dalam indeks teks lengkap.
Repositori JPA diperlukan untuk operasi CRUD pada pengguna di database:
internal interface UserRepository: JpaRepository<User, Long>
Layanan untuk bekerja dengan pengguna, 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 mendapatkan semua pengguna langsung dari database. Pencarian menggunakan komponen userSearch untuk mengambil pengguna dari indeks. Komponen untuk bekerja dengan indeks pencarian pengguna:
@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() } }
Kontroler 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) } }
Kami menggunakan dua metode untuk mengekstrak dari database dan mencari berdasarkan string.
Sebelum aplikasi berjalan, perlu menginisialisasi indeks, kami melakukan ini menggunakan 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()) } } }
Untuk pengujian kami menggunakan 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
Akhirnya,
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') }
Demo ini adalah contoh sederhana menggunakan teknologi Hibernate Search, yang dengannya Anda dapat memahami cara berteman dengan Apache Lucene dan Spring Data Jpa. Jika perlu, proyek berdasarkan demo ini dapat dihubungkan ke Apache Solr atau ElasticSearch. Arah potensial untuk proyek ini adalah untuk mencari indeks besar (> 10 GB) dan mengukur kinerja di dalamnya. Anda dapat membuat konfigurasi untuk ElasticSearch atau konfigurasi indeks yang lebih kompleks dengan menjelajahi kemungkinan Pencarian Hibernate di tingkat yang lebih dalam.
Tautan yang bermanfaat: