Heutzutage entwickeln viele Java-Unternehmensanwendungen mithilfe von Spring Boot. Im Verlauf von Projekten entstehen häufig Aufgaben, Suchmaschinen unterschiedlicher Komplexität zu erstellen. Wenn Sie beispielsweise ein System entwickeln, das Daten über Benutzer und Bücher speichert, ist möglicherweise früher oder später eine Suche nach Benutzername / Nachname, Name / Anmerkung für Bücher erforderlich.

In diesem Beitrag werde ich kurz auf Tools eingehen, die zur Lösung solcher Probleme beitragen können. Und dann werde ich ein Demo-Projekt eines Suchdienstes vorstellen, in dem eine interessantere und komplexere Funktion implementiert ist - die Synchronisation von Entitäten, DB und Suchindex. Am Beispiel dieses Demo-Projekts können Sie sich mit Hibernate Search vertraut machen, einer bequemen Möglichkeit, mit den Volltextindizes Solr, Lucene, ElasticSearch zu kommunizieren.
Unter den Tools für die Bereitstellung von Suchmaschinen würde ich drei herausgreifen.
Lucene ist eine Java-Bibliothek, die eine denormalisierte Datenbankschnittstelle für die Volltextsuche auf niedriger Ebene bietet. Mit seiner Hilfe können Sie Indizes erstellen und diese mit Datensätzen (Dokumenten) füllen. Lesen Sie hier mehr über Lucene.
Solr ist das ultimative Lucene-basierte Softwareprodukt, eine Volltextdatenbank und ein eigenständiger separater Webserver. Es verfügt über eine http-Schnittstelle für die Indizierung und Volltextabfragen, mit der Sie Dokumente indizieren und durchsuchen können. Solr verfügt über eine einfache API und eine integrierte Benutzeroberfläche, sodass keine manuelle Bearbeitung von Indizes erforderlich ist. Auf Habré gab es eine gute
vergleichende Analyse von Solr und Lucene.
ElasticSearch ist ein moderneres Analogon von Solr. Es basiert auch auf Apache Lucene. Im Vergleich zu Solr kann ElasticSearch beim Indizieren von Dokumenten höheren Belastungen standhalten und daher zum Indizieren von Protokolldateien verwendet werden. Im Internet finden Sie eine detaillierte
Tabelle zum Vergleich von Solr und ElasticSearch.
Dies ist natürlich keine vollständige Liste. Oben habe ich nur die Systeme ausgewählt, die die meiste Aufmerksamkeit verdienen. Es gibt viele Systeme zum Organisieren einer Suche. PostgreSQL verfügt über Volltextsuchfunktionen. Vergessen Sie nicht die Sphinx.
Hauptproblem
Wir gehen zur Hauptsache über. Für eine zuverlässige / konsistente Datenspeicherung wird normalerweise RDB (relationale Datenbank) verwendet. Es bietet Transaktionsfähigkeit gemäß den Prinzipien von ACID. Damit die Suchmaschine funktioniert, werden mithilfe eines Index Entitäten und die Felder der Tabellen hinzugefügt, nach denen die Suche durchgeführt wird. Das heißt, wenn ein neues Objekt in das System eintritt, muss es sowohl in der relationalen Datenbank als auch im Volltextindex gespeichert werden.
Wenn die Transaktionsnatur solcher Änderungen in Ihrer Anwendung nicht organisiert ist, können verschiedene Arten der Desynchronisation auftreten. Sie wählen beispielsweise aus einer Datenbank aus, dieses Objekt befindet sich jedoch nicht im Index. Oder umgekehrt: Der Index enthält einen Objektdatensatz, der aus RDB gelöscht wurde.
Es gibt verschiedene Möglichkeiten, um dieses Problem zu lösen. Sie können Transaktionsänderungen mithilfe der Mechanismen JTA und
Spring Transaction Management manuell organisieren. Oder Sie gehen einen interessanteren Weg - verwenden Sie die Suche im Ruhezustand, die alles von selbst erledigt. Standardmäßig wird Lucene verwendet, das Indexdaten im Dateisystem speichert. Im Allgemeinen wird die Verbindung zum Index konfiguriert. Wenn das System gestartet wird, starten Sie die Synchronisationsmethode startAndWait (), und während das System ausgeführt wird, werden die Datensätze in der RDB und im Index gespeichert.
Um diese Lösung zu veranschaulichen, habe ich ein Demo-Projekt mit Hibernate Search vorbereitet. Wir werden einen Dienst erstellen, der Methoden zum Lesen, Aktualisieren und Suchen von Benutzern enthält. Es kann die Basis einer internen Datenbank mit der Möglichkeit der Volltextsuche nach Vorname, Nachname oder anderen Metadaten bilden. Für die Interaktion mit relationalen Datenbanken verwenden wir das
Spring Data Jpa-Framework .
Beginnen wir mit der Entitätsklasse, um den Benutzer darzustellen:
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)
Alles ist Standard, wir bezeichnen Entität mit allen notwendigen Anmerkungen für Federdaten. Mit
Entity geben wir die Entität an, mit
Table geben wir die Bezeichnung in der Datenbank an.
Indizierte Anmerkungen geben an, dass eine Entität indexierbar ist und in einen Volltextindex fällt.
JPA-Repository für CRUD-Vorgänge für Benutzer in der Datenbank erforderlich:
internal interface UserRepository: JpaRepository<User, Long>
Dienst für die Arbeit mit Benutzern, 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 ruft alle Benutzer direkt aus der Datenbank ab. Search verwendet die userSearch-Komponente, um Benutzer aus dem Index abzurufen. Komponente für die Arbeit mit dem Benutzersuchindex:
@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-Controller, 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) } }
Wir verwenden zwei Methoden, um aus der Datenbank zu extrahieren und nach Zeichenfolgen zu suchen.
Bevor die Anwendung ausgeführt wird, muss der Index initialisiert werden. Dies geschieht mit 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()) } } }
Für den Test haben wir PostgreSQL verwendet:
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
Schließlich
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') }
Diese Demo ist ein einfaches Beispiel für die Verwendung der Hibernate Search-Technologie, mit der Sie verstehen, wie Sie Freunde wie Apache Lucene und Spring Data Jpa finden. Bei Bedarf können Projekte, die auf dieser Demo basieren, mit Apache Solr oder ElasticSearch verbunden werden. Eine mögliche Richtung für das Projekt besteht darin, nach großen Indizes (> 10 GB) zu suchen und deren Leistung zu messen. Sie können Konfigurationen für ElasticSearch oder komplexere Indexkonfigurationen erstellen, indem Sie die Möglichkeiten der Ruhezustandsuche auf einer tieferen Ebene untersuchen.
Nützliche Links: