Hoje, muitos estão desenvolvendo aplicativos Java corporativos usando a inicialização por mola. No decorrer dos projetos, muitas vezes surgem tarefas para criar mecanismos de pesquisa de complexidade variável. Por exemplo, se você estiver desenvolvendo um sistema que armazena dados sobre usuários e livros, mais cedo ou mais tarde poderá exigir uma pesquisa por nome de usuário / sobrenome, por nome / anotação de livros.

Neste post, falarei brevemente sobre ferramentas que podem ajudar a resolver esses problemas. E então apresentarei um projeto de demonstração de um serviço de pesquisa, onde um recurso mais interessante e complexo é implementado - sincronização de entidades, banco de dados e índice de pesquisa. Usando o exemplo deste projeto de demonstração, você pode se familiarizar com o Hibernate Search, uma maneira conveniente de se comunicar com os índices de texto completo Solr, Lucene, ElasticSearch.
Entre as ferramentas para implantar mecanismos de pesquisa, eu destacaria três.
O Lucene é uma biblioteca java que fornece uma interface de banco de dados desnormalizada de pesquisa de texto completo de baixo nível. Com sua ajuda, você pode criar índices e preenchê-los com registros (documentos). Leia mais sobre Lucene
aqui .
O Solr é o melhor produto de software baseado em Lucene, um banco de dados de texto completo, um servidor da Web independente. Possui uma interface http para indexação e consultas de texto completo, permite indexar documentos e pesquisá-los. O Solr possui uma API simples e uma interface de usuário incorporada, o que elimina a necessidade de manipulação manual de índices. Em Habré, houve uma boa
análise comparativa de Solr e Lucene.
ElasticSearch é um análogo mais moderno do Solr. Também é baseado no Apache Lucene. Comparado ao Solr, o ElasticSearch pode suportar cargas mais altas ao indexar documentos e, portanto, pode ser usado para indexar arquivos de log. Na rede, você encontra uma
tabela detalhada comparando o Solr e o ElasticSearch.
Obviamente, essa não é uma lista completa; acima, escolhi apenas os sistemas que merecem mais atenção. Existem muitos sistemas para organizar uma pesquisa. O PostgreSQL possui recursos de pesquisa em texto completo; Não se esqueça da Esfinge.
Problema principal
Passamos para a coisa principal. Para armazenamento de dados confiável / consistente, o RDB (banco de dados relacional) geralmente é usado. Fornece transacionalidade de acordo com os princípios do ACID. Para que o mecanismo de pesquisa funcione, é usado um índice, no qual você precisa adicionar entidades e os campos das tabelas pelas quais a pesquisa será realizada. Ou seja, quando um novo objeto entra no sistema, ele deve ser salvo no banco de dados relacional e no índice de texto completo.
Se a natureza transacional de tais alterações não estiver organizada no seu aplicativo, poderão ocorrer vários tipos de dessincronização. Por exemplo, você seleciona de um banco de dados, mas esse objeto não está no índice. Ou vice-versa: existe um registro de objeto no índice e ele foi excluído do RDB.
Existem várias maneiras de resolver esse problema. Você pode organizar manualmente as mudanças transacionais usando os mecanismos JTA e
Spring Transaction Management . Ou você pode seguir uma maneira mais interessante - use a Pesquisa do Hibernate, que fará tudo sozinha. Por padrão, é utilizado o Lucene, que armazena dados de índice dentro do sistema de arquivos; em geral, a conexão com o índice é configurada. Quando o sistema inicia, você inicia o método de sincronização startAndWait () e, enquanto o sistema estiver em execução, os registros serão armazenados no RDB e no índice.
Para ilustrar essa solução, preparei um projeto de demonstração com o Hibernate Search. Criaremos um serviço que contém métodos para ler, atualizar e pesquisar usuários. Pode formar a base de um banco de dados interno com a possibilidade de pesquisa em texto completo por nome, sobrenome ou outros metadados. Para interagir com bancos de dados relacionais, usamos a estrutura
Spring Data Jpa .
Vamos começar com a classe de entidade para representar o usuário:
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)
Tudo é padrão, denotamos entidade com todas as anotações necessárias para os dados da primavera. Usando
Entidade, indicamos a entidade, usando
Tabela, indicamos o rótulo no banco de dados.
A anotação
indexada indica que uma entidade é indexável e se enquadra em um índice de texto completo.
Repositório JPA necessário para operações CRUD em usuários no banco de dados:
internal interface UserRepository: JpaRepository<User, Long>
Serviço para trabalhar com usuários, 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 obtém todos os usuários diretamente do banco de dados. A pesquisa usa o componente userSearch para recuperar usuários do índice. Componente para trabalhar com o índice de pesquisa do usuário:
@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() } }
Controlador 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) } }
Utilizamos dois métodos para extrair do banco de dados e pesquisar por string.
Antes de o aplicativo ser executado, é necessário inicializar o índice, fazemos isso usando 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()) } } }
Para o teste, usamos o 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
Por fim,
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') }
Esta demonstração é um exemplo simples de uso da tecnologia Hibernate Search, com a qual você pode entender como fazer amigos Apache Lucene e Spring Data Jpa. Se necessário, os projetos baseados nesta demonstração podem ser conectados ao Apache Solr ou ElasticSearch. Uma direção potencial para o projeto é procurar índices grandes (> 10 GB) e medir o desempenho neles. Você pode criar configurações para o ElasticSearch ou configurações de índice mais complexas, explorando as possibilidades da Hibernate Search em um nível mais profundo.
Links úteis: