Bootstrap

【Elasticsearch】搜索类型介绍,以及使用SpringBoot实现,并展现给前端

Elasticsearch 提供了多种查询类型,每种查询类型适用于不同的搜索场景。以下是八种常见的 Elasticsearch 查询类型及其详细说明和示例。

1. Match Query

用途:用于全文搜索,会对输入的文本进行分词,并在索引中的字段中查找这些分词。 特点:支持模糊匹配、短语匹配等。

示例

json

GET /my_index/_search
{
  "query": {
    "match": {
      "content": "quick brown fox"
    }
  }
}

2. Term Query

用途:用于精确匹配,不会对输入的文本进行分词。 特点:适用于关键词、ID 等不需要分词的情况。

示例

json

GET /my_index/_search
{
  "query": {
    "term": {
      "status": "active"
    }
  }
}

3. Terms Query

用途:用于在一个字段上匹配多个值。 特点:类似于 SQL 中的 IN 操作符。

示例

json

GET /my_index/_search
{
  "query": {
    "terms": {
      "tags": ["news", "sports"]
    }
  }
}

4. Range Query

用途:用于范围查询,可以指定数值或日期范围。 特点:适用于数值型字段和日期字段。

示例

json

GET /my_index/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 18,
        "lte": 30
      }
    }
  }
}

5. Bool Query

用途:组合多个查询条件,支持 must(必须满足)、should(应该满足)、must_not(必须不满足)和 filter(过滤)子句。 特点:灵活性高,可以构建复杂的查询逻辑。

示例

json

GET /my_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "Elasticsearch" } },
        { "range": { "date": { "gte": "now-1y/d" }}}
      ],
      "filter": [
        { "term": { "status": "published" }}
      ]
    }
  }
}

6. Wildcard Query

用途:使用通配符进行匹配。 特点:支持 *? 通配符,但性能较差,应谨慎使用。

示例

json

GET /my_index/_search
{
  "query": {
    "wildcard": {
      "name": "joh*"
    }
  }
}

7. Fuzzy Query

用途:用于近似匹配,允许一定数量的拼写错误。 特点:适用于拼写错误较多的搜索场景。

示例
GET /my_index/_search
{
  "query": {
    "fuzzy": {
      "username": {
        "value": "kenneth",
        "fuzziness": "AUTO"
      }
    }
  }
}

8. Match Phrase Query

用途:用于精确匹配整个短语。 特点:不会对短语进行分词,要求短语完全一致。

示例

json

GET /my_index/_search
{
  "query": {
    "match_phrase": {
      "description": "Elasticsearch search engine"
    }
  }
}

总结

每种查询类型都有其特定的应用场景和优缺点:

  • Match Query:适合全文搜索。
  • Term Query:适合精确匹配。
  • Terms Query:适合多值匹配。
  • Range Query:适合范围查询。
  • Bool Query:适合复杂查询逻辑。
  • Wildcard Query:适合通配符匹配,性能较低。
  • Fuzzy Query:适合拼写错误匹配。
  • Match Phrase Query:适合精确短语匹配。

通过合理选择和组合这些查询类型,可以实现高效且灵活的搜索功能。

参考资料

这些资源提供了更多关于 Elasticsearch 查询类型的详细信息和示例,帮助你更好地理解和使用这些查询

为了实现八种 Elasticsearch 查询类型并在 Web 前端展示结果,我们可以使用 Spring Boot 构建后端服务,并使用 Thymeleaf 作为模板引擎来构建前端页面。以下是一个完整的示例,展示如何实现这八种查询类型并将结果显示在 Web 前端。

项目结构

spring-boot-elasticsearch-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           ├── controller/
│   │   │           │   └── SearchController.java
│   │   │           ├── model/
│   │   │           │   └── Document.java
│   │   │           ├── repository/
│   │   │           │   └── DocumentRepository.java
│   │   │           ├── service/
│   │   │           │   └── SearchService.java
│   │   │           └── SpringBootElasticsearchDemoApplication.java
│   │   └── resources/
│   │       ├── application.yml
│   │       ├── static/
│   │       └── templates/
│   │           └── search.html
├── pom.xml

1. 添加依赖

pom.xml 中添加必要的依赖:

xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.6.2</version>
    </dependency>
</dependencies>

2. 配置文件

src/main/resources/application.yml 中配置 Elasticsearch 连接信息:

yaml

spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200

server:
  port: 8080

3. 实体类

创建一个实体类 Document.java 表示索引中的文档:

java

package com.example.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Data
@Document(indexName = "documents")
public class Document {
    @Id
    private String id;

    @Field(type = FieldType.Text)
    private String title;

    @Field(type = FieldType.Text)
    private String content;

    @Field(type = FieldType.Integer)
    private Integer age;

    @Field(type = FieldType.Date)
    private String date;

    @Field(type = FieldType.Keyword)
    private String status;

    @Field(type = FieldType.Keyword)
    private String[] tags;

    @Field(type = FieldType.Keyword)
    private String username;
}

4. Repository 接口

创建一个 Repository 接口 DocumentRepository.java

java

package com.example.repository;

import com.example.model.Document;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface DocumentRepository extends ElasticsearchRepository<Document, String> {
}

5. Service 层

创建一个 Service 类 SearchService.java 来处理各种查询逻辑:

java

package com.example.service;

import com.example.model.Document;
import com.example.repository.DocumentRepository;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class SearchService {

    @Autowired
    private RestHighLevelClient client;

    public List<Document> matchQuery(String field, String query) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(field, query);
        sourceBuilder.query(matchQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> termQuery(String field, String value) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(field, value);
        sourceBuilder.query(termQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> termsQuery(String field, List<String> values) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(field, values);
        sourceBuilder.query(termsQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> rangeQuery(String field, Object gte, Object lte) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(field).gte(gte).lte(lte);
        sourceBuilder.query(rangeQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> boolQuery(Map<String, Object> mustQueries, Map<String, Object> shouldQueries, Map<String, Object> mustNotQueries) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        if (mustQueries != null) {
            for (Map.Entry<String, Object> entry : mustQueries.entrySet()) {
                boolQueryBuilder.must(QueryBuilders.matchQuery(entry.getKey(), entry.getValue()));
            }
        }

        if (shouldQueries != null) {
            for (Map.Entry<String, Object> entry : shouldQueries.entrySet()) {
                boolQueryBuilder.should(QueryBuilders.matchQuery(entry.getKey(), entry.getValue()));
            }
        }

        if (mustNotQueries != null) {
            for (Map.Entry<String, Object> entry : mustNotQueries.entrySet()) {
                boolQueryBuilder.mustNot(QueryBuilders.matchQuery(entry.getKey(), entry.getValue()));
            }
        }

        sourceBuilder.query(boolQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> wildcardQuery(String field, String value) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery(field, value);
        sourceBuilder.query(wildcardQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> fuzzyQuery(String field, String value) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery(field, value);
        sourceBuilder.query(fuzzyQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    public List<Document> matchPhraseQuery(String field, String phrase) throws IOException {
        SearchRequest searchRequest = new SearchRequest("documents");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery(field, phrase);
        sourceBuilder.query(matchPhraseQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        return getDocumentsFromResponse(searchResponse);
    }

    private List<Document> getDocumentsFromResponse(SearchResponse searchResponse) {
        List<Document> documents = new ArrayList<>();
        searchResponse.getHits().forEach(hit -> {
            Document document = new Document();
            document.setId(hit.getId());
            document.setTitle((String) hit.getSourceAsMap().get("title"));
            document.setContent((String) hit.getSourceAsMap().get("content"));
            document.setAge((Integer) hit.getSourceAsMap().get("age"));
            document.setDate((String) hit.getSourceAsMap().get("date"));
            document.setStatus((String) hit.getSourceAsMap().get("status"));
            document.setTags((String[]) hit.getSourceAsMap().get("tags"));
            document.setUsername((String) hit.getSourceAsMap().get("username"));
            documents.add(document);
        });
        return documents;
    }
}

6. Controller 层

创建一个 Controller 类 SearchController.java 来处理 HTTP 请求并返回结果:

java

package com.example.controller;

import com.example.model.Document;
import com.example.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
public class SearchController {

    @Autowired
    private SearchService searchService;

    @GetMapping("/search")
    public String search(
            @RequestParam(required = false) String queryType,
            @RequestParam(required = false) String field,
            @RequestParam(required = false) String query,
            @RequestParam(required = false) String value,
            @RequestParam(required = false) String gte,
            @RequestParam(required = false) String lte,
            @RequestParam(required = false) String[] values,
            Model model) throws IOException {

        List<Document> results = null;

        switch (queryType) {
            case "match":
                results = searchService.matchQuery(field, query);
                break;
            case "term":
                results = searchService.termQuery(field, value);
                break;
            case "terms":
                results = searchService.termsQuery(field, List.of(values));
                break;
            case "range":
                results = searchService.rangeQuery(field, gte, lte);
                break;
            case "bool":
                Map<String, Object> mustQueries = new HashMap<>();
                Map<String, Object> shouldQueries = new HashMap<>();
                Map<String, Object> mustNotQueries = new HashMap<>();

                if (field != null && query != null) {
                    mustQueries.put(field, query);
                }

                results = searchService.boolQuery(mustQueries, shouldQueries, mustNotQueries);
                break;
            case "wildcard":
                results = searchService.wildcardQuery(field, value);
                break;
            case "fuzzy":
                results = searchService.fuzzyQuery(field, value);
                break;
            case "match_phrase":
                results = searchService.matchPhraseQuery(field, query);
                break;
            default:
                // Handle default case or throw an exception
                break;
        }

        model.addAttribute("results", results);
        return "search";
    }
}

7. 前端页面

创建一个 Thymeleaf 模板 search.html 来展示搜索结果:

html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Elasticsearch Queries</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
    <h1>Elasticsearch Queries</h1>
    <form method="get" action="/search">
        <div class="form-group">
            <label for="queryType">Query Type:</label>
            <select class="form-control" id="queryType" name="queryType">
                <option value="match">Match</option>
                <option value="term">Term</option>
                <option value="terms">Terms</option>
                <option value="range">Range</option>
                <option value="bool">Bool</option>
                <option value="wildcard">Wildcard</option>
                <option value="fuzzy">Fuzzy</option>
                <option value="match_phrase">Match Phrase</option>
            </select>
        </div>
        <div class="form-group">
            <label for="field">Field:</label>
            <input type="text" class="form-control" id="field" name="field">
        </div>
        <div class="form-group">
            <label for="query">Query:</label>
            <input type="text" class="form-control" id="query" name="query">
        </div>
        <div class="form-group">
            <label for="value">Value:</label>
            <input type="text" class="form-control" id="value" name="value">
        </div>
        <div class="form-group">
            <label for="gte">GTE:</label>
            <input type="text" class="form-control" id="gte" name="gte">
        </div>
        <div class="form-group">
            <label for="lte">LTE:</label>
            <input type="text" class="form-control" id="lte" name="lte">
        </div>
        <div class="form-group">
            <label for="values">Values (comma separated):</label>
            <input type="text" class="form-control" id="values" name="values">
        </div>
        <button type="submit" class="btn btn-primary">Search</button>
    </form>

    <hr>

    <h2>Results:</h2>
    <table class="table table-striped">
        <thead>
        <tr>
            <th>ID</th>
            <th>Title</th>
            <th>Content</th>
            <th>Age</th>
            <th>Date</th>
            <th>Status</th>
            <th>Tags</th>
            <th>Username</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="document : ${results}">
            <td th:text="${document.id}"></td>
            <td th:text="${document.title}"></td>
            <td th:text="${document.content}"></td>
            <td th:text="${document.age}"></td>
            <td th:text="${document.date}"></td>
            <td th:text="${document.status}"></td>
            <td th:text="${#strings.arrayJoin(document.tags, ', ')}"></td>
            <td th:text="${document.username}"></td>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

8. 启动类

创建启动类 SpringBootElasticsearchDemoApplication.java

java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

9. 测试数据

确保 Elasticsearch 中有一些测试数据以便进行查询。你可以使用 Kibana 或 curl 命令插入一些文档到 documents 索引中。

示例文档

json

POST /documents/_doc/1
{
  "title": "Introduction to Elasticsearch",
  "content": "Elasticsearch is a distributed, RESTful search and analytics engine.",
  "age": 5,
  "date": "2023-01-01",
  "status": "published",
  "tags": ["elasticsearch", "search"],
  "username": "john_doe"
}

POST /documents/_doc/2
{
  "title": "Advanced Elasticsearch Techniques",
  "content": "Learn advanced techniques in Elasticsearch for better performance.",
  "age": 3,
  "date": "2023-06-15",
  "status": "draft",
  "tags": ["elasticsearch", "advanced"],
  "username": "jane_smith"
}

10. 运行项目

  1. 启动 Elasticsearch:确保 Elasticsearch 服务器正在运行。
  2. 插入测试数据:使用上述示例文档插入测试数据。
  3. 启动 Spring Boot 应用

bash

./mvnw spring-boot:run

4.访问应用:打开浏览器,访问 http://localhost:8080/search

使用说明

在前端页面中,可以选择不同的查询类型并输入相应的参数,点击“Search”按钮即可查看查询结果。以下是每种查询类型的示例参数:

  • Match Query

    • Query Type: match
    • Field: content
    • Query: introduction
  • Term Query

    • Query Type: term
    • Field: status
    • Value: published
  • Terms Query

    • Query Type: terms
    • Field: tags
    • Values: elasticsearch,search
  • Range Query

    • Query Type: range
    • Field: age
    • GTE: 1
    • LTE: 5
  • Bool Query

    • Query Type: bool
    • Field: status
    • Query: published
  • Wildcard Query

    • Query Type: wildcard
    • Field: username
    • Value: john*
  • Fuzzy Query

    • Query Type: fuzzy
    • Field: username
    • Value: jon_doe
  • Match Phrase Query

    • Query Type: match_phrase
    • Field: title
    • Query: Introduction to Elasticsearch

通过这种方式,你可以在 Web 前端展示和测试八种常见的 Elasticsearch 查询类型。

;