所有分类
  • 所有分类
  • 未分类

Spring Data Elasticsearch–使用实例

简介

说明

spring-data-elasticsearch是比较好用的一个elasticsearch客户端,本文介绍如何使用它来操作ES。本文使用spring-boot-starter-data-elasticsearch,它内部会引入spring-data-elasticsearch。

Spring Data ElasticSearch有下边这几种方法操作ElasticSearch:

  1. ElasticsearchRepository(传统的方法,可以使用)
  2. ElasticsearchRestTemplate(推荐使用。基于RestHighLevelClient)
  3. ElasticsearchTemplate(ES7中废弃,不建议使用。基于TransportClient)
  4. RestHighLevelClient(推荐度低于ElasticsearchRestTemplate,因为API不够高级)
  5. TransportClient(ES7中废弃,不建议使用)

本文仅展示使用ElasticsearchRepository的操作。

官网

官网文档:https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/

本文依赖版本

  • spring-boot-starter-parent:2.3.12.RELEASE
  • spring-boot-starter-data-elasticsearch:2.3.12.RELEASE
    • spring-data-elasticsearch:4.0.9.RELEASE
  • ES服务端:7.15.0

spring-data-elasticsearch:4.0的比较重大的修改:4.0对应支持ES版本为7.6.2,并且弃用了对TransportClient的使用(默认使用High Level REST Client)。

ES从7.x版本开始弃用了对TransportClient的使用,并将会在8.0版本开始完全删除TransportClient。

TransportClient:使用9300端口通过TCP与ES连接,不好用,且有高并发的问题。

High Level REST Client:使用9200端口通过HTTP与ES连接,很好用,性能高。

版本对应大全

Elasticsearch 对于版本的兼容性要求很高,大版本之间是不兼容的。

spring-data-elasticsearch与ES、SpringBoot的对应关系如下。截取自官网

详细的版本对应如下:

Elasticsearch1(Spring Data Elasticsearch 1)
序号Elasticsearch版本Spring Data Elasticsearch版本spring-boot-starter-data-elasticsearch版本
11.1.11.0.0.RELEASE
21.3.21.1.0.RELEASE
31.4.41.2.0.RELEASE
41.5.21.3.0.RELEASE/1.3.x
51.7.31.4.0.M1
Elasticsearch 2(Spring Data Elasticsearch 2)(Sring Boot 1)
序号Elasticsearch版本Spring Data Elasticsearch版本spring-boot-starter-data-elasticsearch版本
12.2.02.0.0.RELEASE/2.0.x
22.4.02.0.4.RELEASE/2.1.x1.5.x
Elasticsearch 5(Spring Data Elasticsearch 3)(Sring Boot 2)
序号Elasticsearch版本Spring Data Elasticsearch版本spring-boot-starter-data-elasticsearch版本
15.4.03.0.0.M4
25.5.03.0.0.RC2/3.0.x2.0.x
Elasticsearch 6(Spring Data Elasticsearch 3)(Sring Boot 2)
序号Elasticsearch版本Spring Data Elasticsearch版本spring-boot-starter-data-elasticsearch版本
16.2.23.1.1.RELEASE/3.1.3.RELEASE /3.1.x2.1.1.RELEASE/2.1.x
26.8.123.2.x2.2.x
Elasticsearch 7(Spring Data Elasticsearch 3)(Sring Boot 2)
序号Elasticsearch版本Spring Data Elasticsearch版本spring-boot-starter-data-elasticsearch版本
17.6.24.0.x2.3.x
27.9.34.1.6/4.1.8/4.1.x2.4.4/2.4.5/2.4.x
37.12.04.2.x2.5.x
47.13.34.3.x2.5.x

公共代码

依赖及配置

主要是这个依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

下边贴出我的整个pom.xml

<?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.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo_spring-data-elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo_spring-data-elasticsearch</name>
    <description>demo_spring-data-elasticsearch</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

配置(application.yml )

spring:
  elasticsearch:
    rest:
      uris: http://127.0.0.1:9200
      # username: xxx
      # password: yyy
      # connection-timeout: 1
      # read-timeout: 30

# 上边是客户端High Level REST Client的配置,推荐使用。

# 下边是reactive客户端的配置,非官方,不推荐。
#  data:
#    elasticsearch:
#      client:
#        reactive:
#          endpoints: 127.0.0.1:9200
#          username: xxx
#          password: yyy
#          connection-timeout: 1
#          socket-timeout: 30
#          use-ssl: false

索引结构

索引结构

http://localhost:9200/
PUT blog

{
    "settings": {
        "number_of_shards": 5,
        "number_of_replicas": 1
    },
    "mappings": {
        "properties": {
            "id":{
                "type":"long"
            },
            "title": {
                "type": "text"
            },
            "content": {
                "type": "text"
            },
            "author":{
                "type": "text"
            },
            "category":{
                "type": "keyword"
            },
            "createTime": {
                "type": "date",
                "format":"yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||epoch_millis"
            },
            "updateTime": {
                "type": "date",
                "format":"yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||epoch_millis"
            },
            "status":{
                "type":"integer"
            },
            "serialNum": {
                "type": "keyword"
            }
        }
    }
}

Entity/Dao

Entity

package com.example.demo.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;

@Data
@Document(indexName = "blog", shards = 1, replicas = 1)
public class Blog {
    //此项作为id,不会写到_source里边。
    @Id
    private Long blogId;

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

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

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

    //博客所属分类。
    @Field(type = FieldType.Keyword)
    private String category;

    //0: 未发布(草稿) 1:已发布 2:已删除
    @Field(type = FieldType.Integer)
    private int status;

    //序列号,用于给外部展示的id
    @Field(type = FieldType.Keyword)
    private String serialNum;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @Field(type= FieldType.Date, format= DateFormat.custom, pattern="yyyy-MM-dd HH:mm:ss.SSS")
    private Date createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @Field(type=FieldType.Date, format=DateFormat.custom, pattern="yyyy-MM-dd HH:mm:ss.SSS")
    private Date updateTime;
}

Dao

package com.example.demo.dao;

import com.example.demo.entity.Blog;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface BlogRepository extends ElasticsearchRepository<Blog, Long> {

}

增删改查(CrudRepository)

简介

接口的继承

所有方法

实例

代码

package com.example.demo.controller;

import com.example.demo.dao.BlogRepository;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Api(tags = "增删改查(文档)")
@RestController
@RequestMapping("crud")
public class CrudController {
    @Autowired
    private BlogRepository blogRepository;

    @ApiOperation("添加单个文档")
    @PostMapping("addDocument")
    public Blog addDocument() {
        Long id = 1L;
        Blog blog = new Blog();
        blog.setBlogId(id);
        blog.setTitle("Spring Data ElasticSearch学习教程" + id);
        blog.setContent("这是添加单个文档的实例" + id);
        blog.setAuthor("Tony");
        blog.setCategory("ElasticSearch");
        blog.setCreateTime(new Date());
        blog.setStatus(1);
        blog.setSerialNum(id.toString());

        return blogRepository.save(blog);
    }

    @ApiOperation("添加多个文档")
    @PostMapping("addDocuments")
    public Object addDocuments(Integer count) {
        List<Blog> blogs = new ArrayList<>();
        for (int i = 1; i <= count; i++) {
            Long id = (long)i;
            Blog blog = new Blog();
            blog.setBlogId(id);
            blog.setTitle("Spring Data ElasticSearch学习教程" + id);
            blog.setContent("这是添加单个文档的实例" + id);
            blog.setAuthor("Tony");
            blog.setCategory("ElasticSearch");
            blog.setCreateTime(new Date());
            blog.setStatus(1);
            blog.setSerialNum(id.toString());
            blogs.add(blog);
        }

        return blogRepository.saveAll(blogs);
    }

    /**
     * 跟新增是同一个方法。若id已存在,则修改。
     * 无法只修改某个字段,只能覆盖所有字段。若某个字段没有值,则会写入null。
     * @return 成功写入的数据
     */
    @ApiOperation("修改单个文档")
    @PostMapping("editDocument")
    public Blog editDocument() {
        Long id = 1L;
        Blog blog = new Blog();
        blog.setBlogId(id);
        blog.setTitle("Spring Data ElasticSearch学习教程" + id);
        blog.setContent("这是修改单个文档的实例" + id);
        // blog.setAuthor("Tony");
        // blog.setCategory("ElasticSearch");
        // blog.setCreateTime(new Date());
        // blog.setStatus(1);
        // blog.setSerialNum(id.toString());

        return blogRepository.save(blog);
    }

    @ApiOperation("查找单个文档")
    @GetMapping("findById")
    public Blog findById(Long id) {
        return blogRepository.findById(id).get();
    }

    @ApiOperation("删除单个文档")
    @PostMapping("deleteDocument")
    public String deleteDocument(Long id) {
        blogRepository.deleteById(id);
        return "success";
    }

    @ApiOperation("删除所有文档")
    @PostMapping("deleteDocumentAll")
    public String deleteDocumentAll() {
        blogRepository.deleteAll();
        return "success";
    }
}

测试1:添加单个文档

head结果

测试2:添加多个文档

head结果

测试3:修改单个文档

head结果

测试4:查找单个文档

测试5:删除单个文档

head结果

测试6:删除所有文档

head结果

自定义方法查询

简介

跟Spring Data JPA类似,spring data elsaticsearch提供了自定义方法的查询方式:

在Repository接口中自定义方法,spring data根据方法名,自动生成实现类,方法名必须符合一定的规则,如下表所示(摘录自官网):

KeywordSampleElasticsearch Query String
AndfindByNameAndPrice{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}
OrfindByNameOrPrice{ “query” : { “bool” : { “should” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}
IsfindByName{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}
NotfindByNameNot{ “query” : { “bool” : { “must_not” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}
BetweenfindByPriceBetween{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
LessThanfindByPriceLessThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } ] } }}
LessThanEqualfindByPriceLessThanEqual{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
GreaterThanfindByPriceGreaterThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } ] } }}
GreaterThanEqualfindByPriceGreaterThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}
BeforefindByPriceBefore{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}
AfterfindByPriceAfter{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}
LikefindByNameLike{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
StartingWithfindByNameStartingWith{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
EndingWithfindByNameEndingWith{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
Contains/ContainingfindByNameContaining{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}
In (when annotated as FieldType.Keyword)findByNameIn(Collection<String>names){ “query” : { “bool” : { “must” : [ {“bool” : {“must” : [ {“terms” : {“name” : [“?”,”?”]}} ] } } ] } }}
InfindByNameIn(Collection<String>names){ “query”: {“bool”: {“must”: [{“query_string”:{“query”: “\”?\” \”?\””, “fields”: [“name”]}}]}}}
NotIn (when annotated as FieldType.Keyword)findByNameNotIn(Collection<String>names){ “query” : { “bool” : { “must” : [ {“bool” : {“must_not” : [ {“terms” : {“name” : [“?”,”?”]}} ] } } ] } }}
NotInfindByNameNotIn(Collection<String>names){“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(\”?\” \”?\”)”, “fields”: [“name”]}}]}}}
NearfindByStoreNearNot Supported Yet !
TruefindByAvailableTrue{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }}
FalsefindByAvailableFalse{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “false”, “fields” : [ “available” ] } } ] } }}
OrderByfindByAvailableTrueOrderByNameDesc{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }, “sort”:[{“name”:{“order”:”desc”}}] }

实例

插入数据

  1. 每个文档必须独占一行,不能换行。
  2. 此命令要放到postman中去执行,如果用head执行会失败

http://localhost:9200/
POST _bulk

{"index":{"_index":"blog","_id":1}}
{"blogId":1,"title":"Spring Data ElasticSearch学习教程1","content":"这是批量添加的文档1","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"1","createTime":"2021-10-10 11:52:01.249","updateTime":null}
{"index":{"_index":"blog","_id":2}}
{"blogId":2,"title":"Spring Data ElasticSearch学习教程2","content":"这是批量添加的文档2","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"2","createTime":"2021-10-10 11:52:02.249","updateTime":null}
{"index":{"_index":"blog","_id":3}}
{"blogId":3,"title":"Spring Data ElasticSearch学习教程3","content":"这是批量添加的文档3","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"3","createTime":"2021-10-10 11:52:03.249","updateTime":null}
{"index":{"_index":"blog","_id":4}}
{"blogId":4,"title":"Spring Data ElasticSearch学习教程4","content":"这是批量添加的文档4","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"4","createTime":"2021-10-10 11:52:04.249","updateTime":null}
{"index":{"_index":"blog","_id":5}}
{"blogId":5,"title":"Spring Data ElasticSearch学习教程5","content":"这是批量添加的文档5","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"5","createTime":"2021-10-10 11:52:05.249","updateTime":null}
{"index":{"_index":"blog","_id":6}}
{"blogId":6,"title":"Java学习教程6","content":"这是批量添加的文档6","author":"Tony","category":"ElasticSearch","status":1,"serialNum":"6","createTime":"2021-10-10 11:52:06.249","updateTime":null}
{"index":{"_index":"blog","_id":7}}
{"blogId":7,"title":"Java学习教程7","content":"这是批量添加的文档7","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"7","createTime":"2021-10-10 11:52:07.249","updateTime":null}
{"index":{"_index":"blog","_id":8}}
{"blogId":8,"title":"Java学习教程8","content":"这是批量添加的文档8","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"8","createTime":"2021-10-10 11:52:08.249","updateTime":null}
{"index":{"_index":"blog","_id":9}}
{"blogId":9,"title":"Java学习教程9","content":"这是批量添加的文档9","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"9","createTime":"2021-10-10 11:52:09.249","updateTime":null}
{"index":{"_index":"blog","_id":10}}
{"blogId":10,"title":"Java学习教程10","content":"这是批量添加的文档10","author":"Pepper","category":"ElasticSearch","status":1,"serialNum":"10","createTime":"2021-10-10 11:52:10.249","updateTime":null}

Repository

package com.example.demo.dao;

import com.example.demo.entity.Blog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface BlogRepository extends ElasticsearchRepository<Blog, Long> {
    List<Blog> findByTitle(String title);

    List<Blog> findByTitleAndContent(String title, String content);

    Page<Blog> findByTitleAndContent(String title, String content, Pageable pageable);
}

Controller

package com.example.demo.controller;

import com.example.demo.dao.BlogRepository;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Api(tags = "自定义方法查询")
@RestController
@RequestMapping("customMethod")
public class CustomMethodController {
    @Autowired
    private BlogRepository blogRepository;

    @ApiOperation("通过标题和内容查询")
    @GetMapping("findByTitleAndContent")
    public void findByTitleAndContent(String title, String content) {
        List<Blog> byTitleList = blogRepository.findByTitle(title);
        List<Blog> byTitleAndContentList = blogRepository
                .findByTitleAndContent(title, content);

        PageRequest page = PageRequest.of(0, 2);
        Page<Blog> byTitleAndContentPage = blogRepository.findByTitleAndContent(title, content, page);

        System.out.println("-----------------------------------------------");
        System.out.println("byTitleList:             " + byTitleList);
        System.out.println("byTitleAndContentList:   " + byTitleAndContentList);
        System.out.println("byTitleAndContentPage:   " + byTitleAndContentPage.getContent());
    }
}

测试1:标题不为空,内容不为空

前端参数

 后端结果:

-----------------------------------------------
byTitleList:             [Blog(blogId=3, title=Spring Data ElasticSearch学习教程3, content=这是批量添加的文档3, author=Tony, category=ElasticSearch, status=1, serialNum=3, createTime=Sun Oct 10 19:52:03 CST 2021, updateTime=null), Blog(blogId=5, title=Spring Data ElasticSearch学习教程5, content=这是批量添加的文档5, author=Tony, category=ElasticSearch, status=1, serialNum=5, createTime=Sun Oct 10 19:52:05 CST 2021, updateTime=null), Blog(blogId=4, title=Spring Data ElasticSearch学习教程4, content=这是批量添加的文档4, author=Tony, category=ElasticSearch, status=1, serialNum=4, createTime=Sun Oct 10 19:52:04 CST 2021, updateTime=null), Blog(blogId=2, title=Spring Data ElasticSearch学习教程2, content=这是批量添加的文档2, author=Tony, category=ElasticSearch, status=1, serialNum=2, createTime=Sun Oct 10 19:52:02 CST 2021, updateTime=null), Blog(blogId=1, title=Spring Data ElasticSearch学习教程1, content=这是批量添加的文档1, author=Tony, category=ElasticSearch, status=1, serialNum=1, createTime=Sun Oct 10 19:52:01 CST 2021, updateTime=null)]
byTitleAndContentList:   [Blog(blogId=2, title=Spring Data ElasticSearch学习教程2, content=这是批量添加的文档2, author=Tony, category=ElasticSearch, status=1, serialNum=2, createTime=Sun Oct 10 19:52:02 CST 2021, updateTime=null)]
byTitleAndContentPage:   [Blog(blogId=2, title=Spring Data ElasticSearch学习教程2, content=这是批量添加的文档2, author=Tony, category=ElasticSearch, status=1, serialNum=2, createTime=Sun Oct 10 19:52:02 CST 2021, updateTime=null)]

测试2:标题不为空,内容为空

前端参数:

后端结果:

-----------------------------------------------
byTitleList:             [Blog(blogId=3, title=Spring Data ElasticSearch学习教程3, content=这是批量添加的文档3, author=Tony, category=ElasticSearch, status=1, serialNum=3, createTime=Sun Oct 10 19:52:03 CST 2021, updateTime=null), Blog(blogId=5, title=Spring Data ElasticSearch学习教程5, content=这是批量添加的文档5, author=Tony, category=ElasticSearch, status=1, serialNum=5, createTime=Sun Oct 10 19:52:05 CST 2021, updateTime=null), Blog(blogId=4, title=Spring Data ElasticSearch学习教程4, content=这是批量添加的文档4, author=Tony, category=ElasticSearch, status=1, serialNum=4, createTime=Sun Oct 10 19:52:04 CST 2021, updateTime=null), Blog(blogId=2, title=Spring Data ElasticSearch学习教程2, content=这是批量添加的文档2, author=Tony, category=ElasticSearch, status=1, serialNum=2, createTime=Sun Oct 10 19:52:02 CST 2021, updateTime=null), Blog(blogId=1, title=Spring Data ElasticSearch学习教程1, content=这是批量添加的文档1, author=Tony, category=ElasticSearch, status=1, serialNum=1, createTime=Sun Oct 10 19:52:01 CST 2021, updateTime=null)]
byTitleAndContentList:   []
byTitleAndContentPage:   []

可以看到,参数只有title的查询才查到了数据,另外两个需要两个参数的查询都没查到数据。

也就是说,自定义方法无法进行动态查询,就算是条件为空,也会放到查询条件中。

测试3:标题不为空,内容为空(但标题条件不是全单词)

前端参数

后端结果

-----------------------------------------------
byTitleList:             []
byTitleAndContentList:   []
byTitleAndContentPage:   []

原因: ElasticSearch是先将内容进行分词,然后再将这些分词创建索引的(即:可以通过分词搜到东西)。对于英文来说,单词就是最小的分词单位了,ES会通过空格、逗号等标点符号来把独立的单词作为分词。比如:“Data”,它就是一个单词,不会再对其进行拆分了,所以,搜整个单词才能搜到。

自定义DSL查询

简介

跟JPA一样,Spring Data ElasticSearch可以使用@Query自定义语句进行查询。

但Spring Data ElasticSearch不能通过冒号指定参数(比如:title),只能用问号加序号(比如?0)。

实例

Repository

package com.example.demo.dao;

import com.example.demo.entity.Blog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface BlogRepository extends ElasticsearchRepository<Blog, Long> {
    // 这样写不行,查不到数据。原因待确定
    // @Query("{\"bool\":{\"must\":[{\"match\":{\"title\":\":titleParam\"}}," +
    //         "{\"match\":{\"content\":\":contentParam\"}}]}}")
    // List<Blog> findByTitleAndContentCustom(@Param("titleParam") String title, @Param("contentParam") String content);

    @Query("{\"bool\":{\"must\":[{\"match\":{\"title\":\"?0\"}}," +
                                "{\"match\":{\"content\":\"?1\"}}]}}")
    List<Blog> findByTitleAndContentCustom(@Param("title") String title, @Param("content") String content);

    @Query("{\"bool\":{\"must\":[{\"match\":{\"title\":\"?0\"}}," +
            "{\"match\":{\"content\":\"?1\"}}]}}")
    Page<Blog> findByTitleAndContentCustom(@Param("title") String title, @Param("content") String content,
                                     Pageable pageable);
}

Controller

package com.example.demo.controller;

import com.example.demo.dao.BlogRepository;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.License;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Api(tags = "自定义DSL查询")
@RestController
@RequestMapping("customDSL")
public class CustomDSLController {
    @Autowired
    private BlogRepository blogRepository;

    @ApiOperation("列表")
    @GetMapping("listByTitleAndContent")
    public List<Blog> listByTitleAndContent(String title, String content) {
        return blogRepository.findByTitleAndContentCustom(title, content);
    }

    @ApiOperation("分页")
    @GetMapping("pageByTitleAndContent")
    public Page<Blog> pageByTitleAndContent(String title, String content) {
        PageRequest pageRequest = PageRequest.of(0, 2);
        return blogRepository.findByTitleAndContentCustom(title, content, pageRequest);
    }
}

测试1:列表。标题不为空,内容不为空

测试2:列表。标题不为空,内容为空

结果:查不到东西。所以自定义DSL不能动态查询。

测试3:分页。标题不为空,内容不为空

1

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录