在Java中搜索:Elasticsearch vs MySQL与Hibernate
#java #hibernate #mysql #elasticsearch

你好,伙计:)

我最近在思考:如果我有搜索前端数据和Java的逻辑以进行后端,则应使用哪种技术来查找数据?在这种情况下,一些搜索引擎将是一个好主意。但是实施容易吗?我想尝试几件事并比较,哪个选项是最好的。
通过这样做,我了解它的工作原理,我想分享这一经验。

LetUsBegin

爪哇

首先,我们需要创建Java应用程序。我使用了春季的“ initializr”来获得我的项目的基本配置。 Spring Initializr

剧透!: - D
我的最终配置看起来像这样(Java版本18):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tutorial</groupId>
    <artifactId>search</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>search</name>
    <description>Tutorial project for search engine</description>
    <properties>
        <java.version>18</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hibernate.search</groupId>
            <artifactId>hibernate-search-mapper-orm</artifactId>
            <version>6.1.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.search</groupId>
            <artifactId>hibernate-search-backend-lucene</artifactId>
            <version>6.1.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-elasticsearch</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>8.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.3</version>
        </dependency>

        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

然后只需安装所有依赖项,我们就可以继续前进。

mvn installmvn package

mysql

好的,现在我们需要我们的数据库和一些数据。要获取数据,我们需要将/安装MySQL到本地计算机。为了使其更轻松,我们可以安装Workbench
也要获得gui。我们需要在 application.properties 中放入有关数据库(用户名,密码,数据库URL)的信息。

spring.datasource.url={url_path}
spring.datasource.username={database_username}
spring.datasource.password={database_password}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

现在只键入 book 模型。我还添加了搜索引擎所需的注释。

LetUsBegin

重要的

一个名为 book 的表格应该存在于您的数据库中,应包括一些测试数据。

@Entity
@Indexed
@Table(name = "book")
@Document(indexName = "books", type = "book")
public class Book {
    @Id
    private int id;

    @FullTextField
    @Field(type = FieldType.Text, name = "name")
    private String name;


    @FullTextField
    @Field(type = FieldType.Text, name = "isbn")
    private String isbn;

    public int getId() {
        return id;
    }

    public String getIsbn() {
        return isbn;
    }

    public String getName() {
        return name;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public void setName(String name) {
        this.name = name;
    }
}

现在要从请求获得响应,我们需要 repository 类。

public interface BookRepository extends PagingAndSortingRepository<Book, Integer>
{
    List<Book> findByName(String name);    
    List<Book> findByIsbn(String isbn);    
}

现在要从URL获取数据,我们需要一个控制器。我只是为了获取响应并检查是否加载数据非常简单。

@RestController
@RequestMapping(value = "book")
public class BookController {    
    @Autowired
    BookRepository bookRepository;

    @GetMapping("/database")
    ResponseEntity<Iterable<Book>> getBooksFromDatabase(@RequestParam("query") String query)
    {
        Iterable<Book> booksFromDatabase = bookRepository.findByIsbn(query);

        return ResponseEntity.ok(booksFromDatabase);
    }
}

好的,它似乎是通过数据库完成的。

Easy

并不是那么难,但这不是与搜索流相互作用的最灵活的选择。

让我们看一下搜索引擎。我们将从Java中的非常强大的技术开始。

冬眠

首先我们需要创建配置文件。

@Component
public class BookIndexConfiguration implements CommandLineRunner
{
    @PersistenceContext
    EntityManager entityManager;

    @Override
    @Transactional(readOnly = true)
    public void run(String ...args) throws Exception
    {
        SearchSession searchSession = Search.session(entityManager);
        MassIndexer indexer = searchSession.massIndexer(Book.class);
        indexer.startAndWait();
    }
} 

在此处,Hibernate的 indexer 将在应用程序启动后使用模型中的数据创建索引。要探索索引中的书籍,我们还需要服务类。好,让我们创建一个。

@Service
public class BookSearchService {
    @PersistenceContext
    EntityManager entityManager;

    @Transactional(readOnly = true)
    public Iterable<Book> search(Pageable pageable, String query)
    {
        SearchSession session = Search.session(entityManager);

        SearchResult<Book> result = session.search(Book.class)
            .where(
                    f -> f.match().
                    fields("name", "isbn").
                    matching(query)
                )
            .fetch((int) pageable.getOffset(), pageable.getPageSize())
        ;
        return result.hits();
    }
}

我们具有搜索函数,该函数加载了当前会话并在索引中检查索引,如果查询与 name isbn name 。如果找到了某些对象,它将以 book book 对象的 itoble 对象返回

那就是这样!典型的数据库调用更容易吗?

现在我们需要配置其他搜索引擎。

Elasticsearch

好吧,现在我们需要更多的配置或可能不是。就像MySQL的情况一样,我们应该安装Elasticsearch。我将使用Docker image获得客户。另外,您可以使用以下链接下的一种方法Elasticsearch install

version: '3.7'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.3.2
    container_name: elasticsearch
    environment:
      - xpack.security.enabled=false
      - discovery.type=single-node
    ports:
      - 9200:9200
      - 9300:9300

重要
为了测试目的,我禁用了安全模式。如果将应用程序部署到实时系统中,则不应执行此操作。

在安装和启动Elasticsearch之后,我们可以使用 client
创建更多配置文件。

public class BookElasticsearchClient {
    private ElasticsearchClient client;

    public ElasticsearchClient getClient()
    {
        if (client == null) {
            initClient();
        }

        return client;
    }

    private void initClient()
    {
        RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)).build();

        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

        client = new ElasticsearchClient(transport);
    }
}

如果您使用用户名和密码的身份验证,则需要配置这些验证。有关如何执行此操作的信息在以下链接下:Authentication

aaaand现在 service 搜索类。


@Configuration
public class BookElasticsearchService {
    BookElasticsearchClient client = new BookElasticsearchClient();

    private static final String BOOK_INDEX = "books";

    public void createBooksIndexBulk(final List<Book> books)
    {
        BulkRequest.Builder builder = new BulkRequest.Builder();

        for (Book book : books)
        {
            builder.operations(op -> op
                .index(index -> index
                    .index(BOOK_INDEX)
                    .id(String.valueOf(book.getId()))
                    .document(book)
                )
            );

            try {
                client.getClient().bulk(builder.build());
            } catch (ElasticsearchException exception) {
                exception.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public List<Book> findBookByIsbn(String query)
    {
        List<Book> books = new ArrayList<>();

        try {
            SearchResponse<Book> search = client.getClient().search(s -> s
                .index(BOOK_INDEX)
                .query(q -> q
                    .match(t -> t
                        .field("isbn")
                        .query(query))),
                Book.class);

            List<Hit<Book>> hits = search.hits().hits();
            for (Hit<Book> hit: hits) {
                books.add(hit.source());
            }

            return books;
        } catch (ElasticsearchException exception) {
            exception.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
}

这是两个非常重要的功能。其中之一是创建索引并将数据放入文档中,另一个已用于从 isbn

获取有关书籍的信息。

,最后但并非最不重要的一点是,我们需要一个存储库类来elasticsearch。

public interface BookElasticsearchRepository extends ElasticsearchRepository<Book, String> {}

最后,我们需要完成控制器以发送请求。

@RestController
@RequestMapping(value = "book")
public class BookController {    
    @Autowired
    BookSearchService searchService;

    @Autowired
    BookRepository bookRepository;

    @Autowired
    BookElasticsearchService bookElasticsearchService;

    @GetMapping("/database")
    ResponseEntity<Iterable<Book>> getBooksFromDatabase(@RequestParam("query") String query)
    {
        Iterable<Book> booksFromDatabase = bookRepository.findByIsbn(query);

        return ResponseEntity.ok(booksFromDatabase);
    }

    @GetMapping("/hibernate")
    ResponseEntity<Iterable<Book>> getBooksFromHibernate(Pageable pageable, @RequestParam("query") String query)
    {
        Iterable<Book> booksFromHibernate = searchService.search(pageable, query);

        return ResponseEntity.ok(booksFromHibernate);
    }

    @GetMapping("/elasticsearch")
    ResponseEntity<Iterable<Book>> getBooksFromElasticsearch(@RequestParam("query") String query)
    {
        Iterable<Book> booksFromElasticSearch = bookElasticsearchService.findBookByIsbn(query);

        return ResponseEntity.ok(booksFromElasticSearch);
    }
}

启动应用程序之前的一件更重要的事情:
我们需要在Main ApplicationClass 中添加注释(见下文),以使其与一个应用程序中的多个搜索引擎技术一起使用。

@EnableElasticsearchRepositories(basePackageClasses = BookElasticsearchRepository.class)
@EnableJpaRepositories(excludeFilters = @ComponentScan.Filter(
    type = FilterType.ASSIGNABLE_TYPE, value = BookElasticsearchRepository.class
))
@SpringBootApplication
public class SearchApplication {

    public static void main(String[] args) {
        SpringApplication.run(SearchApplication.class, args);
    }

}

让我们一起测试所有事物。

我也必须在第一次创建一个索引。我从 bookelasticsearchservice 中称函数 createbooksindexbulk 。还有一个可以直接在Elasticsearch Console中调用索引的选项。在这里描述了如何做到这一点:Bulk Api

我发送夫妇请求(在我的数据库中,我有一个 isbn = 123 -test ):

http://localhost:8080/book/hibernate?query=123test
http://localhost:8080/book/database?query=123test
http://localhost:8080/book/elasticsearch?query=123test

对于每个请求,我都会得到正确的答复。任务完成。

MySQL

Hibernate

Elasticsearch

ItsWorking

非常感谢您的阅读。

我使用的其他资源:

您也可以检查我的github以获取整个代码

GitHub logo Yordaniss / compare-search-engine

存储库比较Java应用程序的搜索引擎

Compare search engine for JAVA

This repository was created to test implementation of popular search engines with directly MySQL loading for JAVA Spring application.

Sources for data loading:

  • Hibernate Search
  • Elasticsearch
  • MySQL