BOOT 整合 ElasticSearch

Posted 小影~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BOOT 整合 ElasticSearch相关的知识,希望对你有一定的参考价值。

整合前先理解几个概念 与关键字

  • 索引<Index> 一组相似文档的集合

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个商品数据的索引,一个订单数据的索引,还有一个用户数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。

  • 映射<Mapping> 用来定义索引存储文档的结构:字段、类型等

映射是定义一个文档和它所包含的字段如何被存储和索引的过程。在默认配置下,ES可以根据插入的数据自动地创建mapping,也可以手动创建mapping。 mapping中主要包括字段名、字段类型等

  • 文档<Document> 索引中一条记录,可以被索引的最小单元

文档是索引中存储的一条条数据。一条文档是一个可被索引的最小单元。ES中的文档采用了轻量级的JSON格式数据来表示。

  • 分片<shards>

Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置 到集群中的任何节点上。

  • 复制<replicas>

Index的分片中一份或多份副本。


 关键字对比

ElasticSearch

对比

must

相当于mysql的and

should

相当于MySQL的or

must_not

相当于MySQL的not不等于

gt        大于
git         大于等于
lt 小于
lte 小于等于

match

分词查询

term

完全匹配

fuzzy 误拼写查询
range         范围查询
wildcard 模糊查询
exists 排除null
match_phrase 短语匹配
match_phrase和match区别 match是分词匹配(将输入的词进行分词),match_phrase是短语匹配(输入文本进行查找)


ES存储类型:

文本类型 - text

在Elasticsearch 5.4 版本开始, text取代了需要分词的string, 当一个字段需要用于全文搜索(会被分词), 比如产品名称、产品描述信息, 就应该使用text类型.

text的内容会被分词, 可以设置是否需要存储: “index”: “true|false” 。text类型的字段不能用于排序, 也很少用于聚合.

PUT website

	"mappings": 
        "blog": 
            "properties": 
        		"summary": "type": "text", "index": "true"
            
        
    

 关键字类型 - keyword

在Elasticsearch 5.4 版本开始, keyword取代了不需要分词的string.—— 当一个字段需要按照精确值进行过滤、排序、聚合等操作时, 就应该使用keyword类型.

keyword的内容不会被分词, 可以设置是否需要存储: “index”: “true|false”.

PUT website

	"mappings": 
        "blog": 
            "properties": 
        		"tags": "type": "keyword", "index": "true"
            
        
    

数字类型 - 8种

类型

说明

byte

有符号的8位整数, 范围: [-128 ~ 127]

short

有符号的16位整数, 范围: [-32768 ~ 32767]

integer

有符号的32位整数, 范围: [−231 ~ 231-1]

long

有符号的64位整数, 范围: [−263 ~ 263-1]

float

32位单精度浮点数

double

64位双精度浮点数

half_float

16位半精度IEEE 754浮点类型

scaled_float

缩放类型的的浮点数, 比如price字段只需精确到分, 57.34缩放因子为100, 存储结果为5734

使用注意事项:

尽可能选择范围小的数据类型, 字段的长度越短, 索引和搜索的效率越高;优先考虑使用带缩放因子的浮点类型.

PUT shop

    "mappings": 
        "book": 
            "properties": 
                "name": "type": "text",
                "quantity": "type": "integer",  // integer类型
                "price": 
                    "type": "scaled_float",       // scaled_float类型
                    "scaling_factor": 100
                
            
        
    

 日期类型 - date

JSON没有日期数据类型, 所以在ES中, 日期可以是:包含格式化日期的字符串, “2018-10-01”, 或"2018/10/01 12:10:30"

代表时间毫秒数的长整型数字。代表时间秒数的整数.

如果时区未指定, 日期将被转换为UTC格式, 但存储的却是长整型的毫秒值.

可以自定义日期格式, 若未指定, 则使用默认格式: strict_date_optional_time||epoch_millis

 // 添加映射
PUT website

    "mappings": 
        "blog": 
            "properties": 
                "pub_date": "type": "date"   // 日期类型
            
        
    


// 添加数据
PUT website/blog/11
 "pub_date": "2018-10-10" 

PUT website/blog/12
 "pub_date": "2018-10-10T12:00:00Z" 	// Solr中默认使用的日期格式

PUT website/blog/13
 "pub_date": "1589584930103" 			// 时间的毫秒值

(2) 多种日期格式:

多个格式使用双竖线||分隔, 每个格式都会被依次尝试, 直到找到匹配的.第一个格式用于将时间毫秒值转换为对应格式的字符串.

/ 添加映射
PUT website

    "mappings": 
        "blog": 
            "properties": 
                "date": 
                    "type": "date",  // 可以接受如下类型的格式
                    "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
                
            
        
    

布尔类型 - boolean

可以接受表示真、假的字符串或数字:

真值: true, “true”, “on”, “yes”, “1”…

假值: false, “false”, “off”, “no”, “0”, “”(空字符串), 0.0, 0

二进制型 - binary

二进制类型是Base64编码字符串的二进制值, 不以默认的方式存储, 且不能被搜索. 有2个设置项:

(1) doc_values: 该字段是否需要存储到磁盘上, 方便以后用来排序、聚合或脚本查询. 接受true和false(默认);

(2) store: 该字段的值是否要和_source分开存储、检索, 意思是除了_source中, 是否要单独再存储一份. 接受true或false(默认).

// 添加映射
PUT website

    "mappings": 
        "blog": 
            "properties": 
                "blob": "type": "binary"   // 二进制
            
        
    

// 添加数据
PUT website/blog/1

    "title": "Some binary blog",
    "blob": "hED903KSrA084fRiD5JLgY=="

注意: Base64编码的二进制值不能嵌入换行符\\n, 逗号(0x2c)等符号.

 范围类型 - range

range过滤允许我们按照制定范围查找一批数据

复杂数据类型Array

ES中没有专门的数组类型, 直接使用[]定义即可;

数组中所有的值必须是同一种数据类型, 不支持混合数据类型的数组:

① 字符串数组: [“one”, “two”]; ② 整数数组: [1, 2]; ③ 由数组组成的数组: [1, [2, 3]], 等价于[1, 2, 3]; ④ 对象数组: [“name”: “Tom”, “age”: 20, “name”: “Jerry”, “age”: 18].

注意:

  • 动态添加数据时, 数组中第一个值的类型决定整个数组的类型;
  • 不支持混合数组类型, 比如[1, “abc”];
  • 数组可以包含null值, 空数组[]会被当做missing field —— 没有值的字段.

复杂数据类型Object

JSON文档是分层的: 文档可以包含内部对象, 内部对象也可以包含内部对象.

复杂数据类型嵌套类型 - nested

嵌套类型是对象数据类型的一个特例, 可以让array类型的对象被独立索引和搜索.

专门数据类型IP类型

IP类型的字段用于存储IPv4或IPv6的地址, 本质上是一个长整型字段.

添加映射

PUT employee

    "mappings": 
        "customer": 
            "properties": 
                "ip_addr":  "type": "ip" 
            
        
    

添加数据

PUT employee/customer/1
 "ip_addr": "192.168.1.1" 

计数数据类型 - token_counttoken_count类型用于统计字符串中的单词数量.

添加映射

PUT employee

    "mappings": 
        "customer": 
            "properties": 
                "name":  
                    "type": "text",
                    "fields": 
                        "length": 
                            "type": "token_count", 
                            "analyzer": "standard"
                        
                    
                
            
        
    

添加数据:

PUT employee/customer/1
 "name": "John Snow" 
PUT employee/customer/2
 "name": "Tyrion Lannister" 

 查询数据

GET employee/customer/_search

    "query": 
        "term":  "name.length": 2 
    

整合SpringBoot 

整合前熟悉几个重要的对象与构造器

SearchRequest 查询请求对象
SearchResponse 查询响应对象
SearchSourceBuilder 查询源构建器
MatchQueryBuilder 匹配查询构建器

BoolQueryBuilder

布尔查询构建器

TermQueryBuilder

词条查想构建器

QueryBuilders

查询构建器工厂

AggregationBuilders

聚合构建器工厂

TremsAggregationBuilder

词条聚合构造器

Aggregations

分组结果封装

Terms.Bucket

Spring boot中主要有Java REST Clientspring-data-elasticsearch两种方式可以整合ES 本文使用spring-data-elasticsearch

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

配置文件

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration 
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() 
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("localhost:9200")
                .build();
        return RestClients.create(clientConfiguration).rest();
    

 创建索引 并 映射 & 删除索引 

put /products   #products索引名称

    "mappings": 
        "properties": 
            "title":   // 字段名称
                "type": "keyword" // 类型
            ,
            "price":  // 字段名称
                "type": "double" // 类型
            ,
            "created_time":  // 字段名称
                "type": "date" // 类型
            ,
            "description":  // 字段名称
                "type": "text", // 类型
                "analyzer": "ik_max_word" // 开启分词器
            
        
    


#删除索引
DELETE /products

 Boot

@Resource
private RestHighLevelClient restHighLevelClient;
/**
 *  创建索引 创建映射
 */
@Test
void indexAndMapping() throws IOException 
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("products");
    createIndexRequest.mapping("\\n" +
            "    \\"properties\\": \\n" +
            "      \\"title\\":\\n" +
            "        \\"type\\": \\"keyword\\"\\n" +
            "      ,\\n" +
            "      \\"price\\":\\n" +
            "        \\"type\\": \\"double\\"\\n" +
            "      ,\\n" +
            "      \\"created_time\\":\\n" +
            "        \\"type\\": \\"date\\"\\n" +
            "      ,\\n" +
            "      \\"description\\":\\n" +
            "        \\"type\\": \\"text\\",\\n" +
            "        \\"analyzer\\":\\"ik_max_word\\"\\n" +
            "      \\n" +
            "    \\n" +
            "  ", XContentType.JSON);//指定映射参数1:指定映射json结构  参数2:指定数据类型
    CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    System.out.println("创建状态: "+createIndexResponse.isAcknowledged());
    restHighLevelClient.close();//关闭资源


/**
 * 删除索引
 */
@Test
public void testDeleteIndex() throws IOException 
    //参数 1: 删除索引对象  参数 2:请求配置对象
    AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(new DeleteIndexRequest("products"), RequestOptions.DEFAULT);
    System.out.println(acknowledgedResponse.isAcknowledged());

 添加 & 修改 数据

/**
 * @author 小影
 * @create 2022-05-09 9:51
 * @describe:商品
 */
@Data
public class Product 
    private Integer id;
    // 标题
    private String title;
    // 价格
    private Double price;
    // 创建时间
    private LocalDateTime createTime;
    // 描述
    private String description;
/**
 * 添加数据(文档)
 * @throws IOException
 */
@Test
void testCreate() throws IOException 
    // param:索引名称
    IndexRequest indexRequest = new IndexRequest("products");
    // 要存储数据
    Product product = new Product();
    product.setId(1);
    product.setTitle("蜜汁小汉堡");
    product.setCreateTime(LocalDateTime.now());
    product.setPrice(3.0);
    product.setDescription("老八秘制小汉堡,好吃又管饱");

    indexRequest.id(product.getId().toString());// 存储ID,不设置默认随机字符串
    indexRequest.source(JSONUtil.toJsonStr(product), XContentType.JSON);//指定文档数据
    //参数 1: 索引请求对象  参数 2:请求配置对象
    IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
    System.out.println(indexResponse.status());// CREATED:成功

修改和添加是一样的,传入的参数中的ID在es中存在即为修改,不同的是修改成功indexResponse.status()返回的 是OK,如果是添加返回的是CREATE

 删除数据

/**
 * 删除数据(文档)
 */
@Test
public void testDelete() throws IOException 
    //参数 1: 删除请求对象 参数 2: 请求配置对象
    DeleteRequest deleteRequest = new DeleteRequest("products", "5W25poAB9vN9HF984Etg");
    DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
    System.out.println(deleteResponse.status());// 成功返回OK

根据ID查询数据

/**
 * 基于id 查询数据(文档)
 */
@Test
public void testQueryById() throws IOException 
    GetRequest getRequest = new GetRequest("products", "1");
    //参数 1: 查询请求对象 参数 2:请求配置对象  返回值: 查询响应对象
    GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
    System.out.println("id: "+getResponse.getId());
    System.out.println("source: "+getResponse.getSourceAsString());

 查询所有数据

/**
 * 查询所有数据(文档)
 * 查询所有matchAllQuery
 */
@Test
public void testMatchAll() throws IOException 
    SearchRequest searchRequest = new SearchRequest("products"); //指定搜索索引
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//指定条件对象
    sourceBuilder.query(QueryBuilders.matchAllQuery());//查询所有
    searchRequest.source(sourceBuilder);//指定查询条件
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);//参数 1:搜索请求对象 参数2: 请求配置对象 返回值:查询结果对象
    System.out.println("总条数: " + searchResponse.getHits().getTotalHits().value);
    System.out.println("最大得分: " + searchResponse.getHits().getMaxScore());
    //获取结果
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) 
        String id = hit.getId();
        System.out.println("id: "+ id +" source: "+hit.getSourceAsString());
    

 

 不同条件查询(关键字、范围、前缀、通配符、多个ID、多个字段)

注意:es只能匹配单字符 不能搜索词语的问题 ES默认分词规则不能支持中文,通过安装IK Analysis for Elasticsearch支持中文分词。

分词器下载地址,切记要与es版本一致:Releases · medcl/elasticsearch-analysis-ik · GitHub

/**
 * 不同条件查询 term(关键词查询)
 */
@Test
public void testQuery() throws IOException 
    //1.term 关键词  只能匹配单字符 不能搜索词语的问题 ES默认分词规则不能支持中文,通过安装IK Analysis for Elasticsearch支持中文分词。
    //query(QueryBuilders.termQuery("description","小汉堡"));
    //2.range 范围 价格在0-6之间的数据
    //query(QueryBuilders.rangeQuery("price").gt(0).lte(6));
    //3.prefix 前缀
    //query(QueryBuilders.prefixQuery("title","s蜜汁"));
    //4.wildcard 通配符查询 ? 一个字符  * 任意多个字符
    //query(QueryBuilders.wildcardQuery("title","*小汉堡*"));
    //5.ids 多个指定 id 查询
    //query(QueryBuilders.idsQuery().addIds("1").addIds("2"));
    //6.multi_match 多字段查询
    query(QueryBuilders.multiMatchQuery("豆腐","title","description"));


public void query(QueryBuilder queryBuilder) throws IOException 
    SearchRequest searchRequest = new SearchRequest("products");//指定搜索索引
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//指定条件对象
    sourceBuilder.query(queryBuilder);//指定查询条件
    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//参数 1:搜索请求对象 参数2: 请求配置对象 返回值:查询结果对象
    System.out.println("符合条件总数: "+searchResponse.getHits().getTotalHits().value);
    System.out.println("获取文档最大分数: "+searchResponse.getHits().getMaxScore());

    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) 
        System.out.println("id: "+hit.getId()+" source: "+hit.getSourceAsString());
    

 分页、排序、指定返回字段、高亮结果

/**
 * 查询所有
 * 分页查询 form 起始位置默认0开始   size 每页展示记录数
 * 排序 sort
 * 返回指定的字段 fetchSource  用来指定查询文档返回那些字段
 * 高亮结果  highlighter
 */
@Test
public void testSearch() throws IOException 
    SearchRequest searchRequest = new SearchRequest("products");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //创建高亮器
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.requireFieldMatch(false).field("description").field("title").preTags("<span style=\'color:red;\'>").postTags("</span>");
    sourceBuilder.query(QueryBuilders.termQuery("description","简简单单"))
            .from(0) //起始位置 start = (page-1)*size
            .size(10)//每页显示条数 默认返回 10条
            .sort("price", SortOrder.ASC)//指定排序字段 参数 1: 根据哪个字段排序  参数 2:排序方式 [asc,desc]
            .fetchSource(new String[],new String[]"create_time")//参数 1: 包含字段数组  参数 2:排除字段数组
            .highlighter(highlightBuilder);//高亮搜索结果
    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

    System.out.println("符合条件总数: "+searchResponse.getHits().getTotalHits().value);
    System.out.println("获取文档最大分数: "+searchResponse.getHits().getMaxScore());

    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) 
        System.out.println("id: "+hit.getId()+" source: "+hit.getSourceAsString());
        //获取高亮字段
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if (highlightFields.containsKey("description"))
            System.out.println("description高亮结果: "+highlightFields.get("description").fragments()[0]);
        
        if (highlightFields.containsKey("title"))
            System.out.println("title高亮结果: "+highlightFields.get("title").fragments()[0]);
        

    

 过滤查询

过滤查询<filter query>,其实准确来说,ES中的查询操作分为2种: 查询(query)和过滤(filter)。查询即是之前提到的query查询,它 (查询)默认会计算每个返回文档的得分,然后根据得分排序。而过滤(filter)只会筛选出符合的文档,并不计算 得分,而且它可以缓存文档 。所以,单从性能考虑,过滤比查询更快。 换句话说过滤适合在大范围筛选数据,而查询则适合精确匹配数据。一般应用时, 应先使用过滤操作过滤数据, 然后使用查询匹配数据。

/**
 * query            : 查询精确查询  查询计算文档得分 并根据文档得分进行返回
 * filter query     : 过滤查询  用来在大量数据中筛选出本地查询相关数据  不会计算文档得分 经常使用 filter query 结果进行缓存
 * 注意: 一旦使用 query 和 filterQuery  es 优先执行 filter Query 然后再执行 query 所以使用filter query 查询会比较快
 */
@Test
public void testFilterQuery() throws IOException 
    SearchRequest searchRequest = new SearchRequest("products");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder
            .query(QueryBuilders.termQuery("description","老八"))
            .postFilter(QueryBuilders.idsQuery().addIds("1").addIds("2").addIds("3"));//用来指定过滤条件
    searchRequest.source(sourceBuilder);

    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

    System.out.println("符合条件总数: "+searchResponse.getHits().getTotalHits().value);
    System.out.println("获取文档最大分数: "+searchResponse.getHits().getMaxScore());

    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) 
        System.out.println("id: "+hit.getId()+" source: "+hit.getSourceAsString());
    

 聚合查询 max(ParsedMax)、 min(ParsedMin) 、sum(ParsedSum)、 avg(ParsedAvg)

es语法:

先根据某个字段分组 此案例中根据价格分组

GET /products/_search

  "query": 
    "term": 
      "title": 
        "value": "老八米饭"
      
    
  ,
  "aggs": 
    "price_gooup": 
      "terms": 
        "field": "price"
      
    
  

之后就可以操作

求最大值

GET /products/_search

  "aggs": 
    "price_max": 
      "max": 
        "field": "price"
      
    
  

 求最小值

GET /products/_search

  "aggs": 
    "price_min": 
      "min": 
        "field": "price"
      
    
  

求平均值

GET /products/_search

  "aggs": 
    "price_avg": 
      "avg": 
        "field": "price"
      
    
  

....

/**
 * max(ParsedMax) min(ParsedMin) sum(ParsedSum) avg(ParsedAvg) 聚合函数  桶中只有一个返回值
 */
@Test
public void testAggsFunction() throws IOException 
    SearchRequest searchRequest = new SearchRequest("products");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder
            .query(QueryBuilders.matchAllQuery()) //查询条件
            //.aggregation(AggregationBuilders.sum("price_sum").field("price"))//用来设置聚合处理 sum
            //.aggregation(AggregationBuilders.avg("price_avg").field("price")) //用来设置聚合处理 avg
            .aggregation(AggregationBuilders.max("price_min").field("price")) //max
            .size(0);//默认查询所有进行聚合
    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    Aggregations aggregations = searchResponse.getAggregations();
    ParsedMax parsedMax = aggregations.get("price_min");
    System.out.println(parsedMax.getValue());

nested嵌套对象 关联查询

需求:通过传递一系列评论来存储博客文章及其所有评论。数据结构如下:


  "title": "牛人集结地",
  "body": "随便评论",
  "comments": [
    
      "name": "张三",
      "age": 33,
      "rating": 6,
      "comment": "找我牛逼",
      "commented_on": "22 Nov 2022"
    ,
    
      "name": "老八",
      "age": 33,
      "rating": 9,
      "comment": "吃俩柠檬整点蒜,简简单单一顿饭,美食界里我老八,今天给你们吃沙拉",
      "commented_on": "25 Nov 2022"
    ,
    
      "name": "吕小布",
      "age": 35,
      "rating": 6,
      "comment": "踏遍青楼人未老,请用汇仁肾宝",
      "commented_on": "21 Nov 2022"
    
  ]
#创建索引
PUT /blog_new

  "mappings": 
      "properties":
        "title":
          "type":"text"
        ,
        "body":
          "type":"text"
        ,
        "comments":
          "type":"nested",
          "properties": 
            "name": 
              "type": "text"
            ,
            "comment": 
              "type": "text"
            ,
            "age": 
              "type": "short"
            ,
            "rating": 
              "type": "short"
            ,
            "commented_on": 
              "type": "text"
            
          
        
      
  
/**
 * 添加关联信息
 */
@Test
public void addParent() throws IOException 
    IndexRequest indexRequest = new IndexRequest("blog_new");
    String id = UUID.randomUUID().toString();
    Blog blog = new Blog();
    blog.setBody("随便评论");
    blog.setTitle("牛人集结地");

    List<Comments> list = new ArrayList<>();

    Comments comments1 = new Comments();
    comments1.setName("张三");
    comments1.setAge(33);
    comments1.setRating(6);
    comments1.setComment("找我牛逼");
    comments1.setCommented_on("22 Nov 2022");

    Comments comments2 = new Comments();
    comments2.setName("老八");
    comments2.setAge(33);
    comments2.setRating(9);
    comments2.setComment("吃俩柠檬整点蒜,简简单单一顿饭,美食界里我老八,今天给你们吃沙拉");
    comments2.setCommented_on("25 Nov 2022");

    Comments comments3 = new Comments();
    comments3.setName("吕小布");
    comments3.setAge(35);
    comments3.setRating(6);
    comments3.setComment("踏遍青楼人未老,请用汇仁肾宝");
    comments3.setCommented_on("21 Nov 2022");

    list.add(comments1);
    list.add(comments2);
    list.add(comments3);

    blog.setComments(list);
    String source = JSON.toJSONString(blog);
    System.out.println("source = " + source);
    indexRequest.id("W_DKq4ABHdN8KsLXQ2ZR");
    indexRequest.source(source, XContentType.JSON);
            IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
    System.out.println(indexResponse.status());

 

 查询:

@Test
public void testGet() throws IOException 
    SearchRequest request = new SearchRequest("blog_new"); // 初始化需要查询的索引
    //查询student 字段下的hobby字段下的name字段的值 等于篮球的学生,这里特别注意第一层必须是你查询的字段的前一层字段
    // 查询条件1 comment存在老八
    QueryBuilder one = QueryBuilders.nestedQuery("comments", QueryBuilders.boolQuery()
            .must(QueryBuilders.matchQuery("comments.comment", "老八")), ScoreMode.Total);
    // 并且年龄在33
    QueryBuilder two = QueryBuilders.nestedQuery("comments", QueryBuilders.boolQuery()
            .must(QueryBuilders.matchQuery("comments.age", "33")), ScoreMode.Total);
    
    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); // 初始化 最后组合成的条件

    queryBuilder.must(one);
    queryBuilder.must(two);

    //初始化条件builer
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //加入组合成的条件到builder
    searchSourceBuilder.query(queryBuilder);
    //设置查询的分页 第一个从0开始
    searchSourceBuilder.from(0);
    //每页的大小为2
    searchSourceBuilder.size(2);
    //初始化索引中加入 builder
    request.source(searchSourceBuilder);
    try 
        SearchResponse searchResponse = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = searchResponse.getHits().getHits();
        //这里把数据转成实体
        List<Blog> res = new ArrayList<>();
        for (SearchHit hit : hits) 
            res.add(JSON.parseObject(hit.getSourceAsString(), Blog.class));
        
        System.out.println(JSON.toJSONString(res));
     catch (Exception e) 
        e.printStackTrace();
    

缺点:更新某个内容只能把这条数据,都替换,删除某个子对象也是


自己根据案例改的工具类

/**
 * @author 小影
 * @create 2022-05-09 14:54
 * @describe:ElasticSearch工具类
 */
@Component
public class EsUtil 
    private static final Logger logger = LoggerFactory.getLogger(EsUtil.class);
    @Resource
    private RestHighLevelClient restHighLevelClient;

    /**
     * 判断一个索引是否存在
     *
     * @param indexName
     * @return
     */
    public boolean isExistsIndex(String indexName) 
        GetIndexRequest request = new GetIndexRequest();
        try 
            boolean exists = restHighLevelClient.indices().exists(request.indices(indexName), RequestOptions.DEFAULT);
            return exists;
         catch (IOException e) 
            e.printStackTrace();
        
        return false;
    

    /**
     * 删除索引
     *
     * @param indexName 索引名称
     * @return
     * @throws IOException
     */
    public boolean DeleteIndex(String indexName) 
        //参数 1: 删除索引对象  参数 2:请求配置对象
        AcknowledgedResponse acknowledgedResponse = null;
        try 
            acknowledgedResponse = restHighLevelClient.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT);
         catch (IOException e) 
            e.printStackTrace();
        
        return acknowledgedResponse.isAcknowledged() == true ? true : false;
    

    /**
     * 添加数据 & 修改数据
     *
     * @param indexName 索引名称
     * @param id        数据id
     * @param dataJson  要存储的数据JSON格式
     * @return ID在es库中不存在即为添加, 存在修改
     */
    public boolean createData(String indexName, String id, String dataJson) 
        try 
            // param:索引名称
            IndexRequest indexRequest = new IndexRequest(indexName);
            indexRequest.id(id);// 存储ID,不设置默认随机字符串
            indexRequest.source(dataJson, XContentType.JSON);//指定文档数据
            //参数 1: 索引请求对象  参数 2:请求配置对象
            IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            System.err.println(indexResponse);
            System.out.println(indexResponse.status());
            if (indexResponse.status() == RestStatus.CREATED || indexResponse.status() == RestStatus.OK) 
                return true;
            
         catch (IOException e) 
            e.printStackTrace();
        
        return false;
    

    /**
     * 根据ID删除数据
     *
     * @param indexName 索引名称
     * @param id        ID
     * @return
     */
    public boolean deleteDataById(String indexName, String id) 
        DeleteResponse deleteResponse = null;
        try 
            DeleteRequest deleteRequest = new DeleteRequest(indexName, id);
            deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
         catch (IOException e) 
            e.printStackTrace();
        
        return deleteResponse.status() == RestStatus.OK;
    

    /**
     * 根据ID查询数据(文档)
     *
     * @param indexName
     * @param id
     * @return
     */
    public String queryById(String indexName, String id) 
        try 
            GetRequest getRequest = new GetRequest(indexName, id);
            //参数 1: 查询请求对象 参数 2:请求配置对象  返回值: 查询响应对象
            GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
            return getResponse.getSourceAsString();
         catch (IOException e) 
            e.printStackTrace();
        
        return null;
    

    /**
     * 查询所有:查询、分页
     *
     * @param indexName 索引名称
     * @param pageNo    页码
     * @param pageSize  页大小
     * @return
     */
    public SearchHit[] queryAll(String indexName, Integer pageNo, Integer pageSize) 
        SearchResponse searchResponse = null;//参数 1:搜索请求对象 参数2: 请求配置对象 返回值:查询结果对象
        try 
            SearchRequest searchRequest = new SearchRequest(indexName); //指定搜索索引
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//指定条件对象
            sourceBuilder.query(QueryBuilders.matchAllQuery())//查询所有
                    .from((pageNo - 1) * pageSize)
                    .size(pageSize);
            searchRequest.source(sourceBuilder);//指定查询条件
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
         catch (IOException e) 
            e.printStackTrace();
        
        return searchResponse.getHits().getHits();
    

    /**
     * 调用通用查询借鉴
     *
     * @throws IOException
     */
    public void testQuery() throws IOException 
        //1.term 关键词  只能匹配单字符 不能搜索词语的问题 ES默认分词规则不能支持中文,通过安装IK Analysis for Elasticsearch支持中文分词。
        //query(QueryBuilders.termQuery("description","小汉堡"));
        //2.range 范围 价格在0-6之间的数据
        //query(QueryBuilders.rangeQuery("price").gt(0).lte(6));
        //3.prefix 前缀
        //query(QueryBuilders.prefixQuery("title","s蜜汁"));
        //4.wildcard 通配符查询 ? 一个字符  * 任意多个字符
        //query(QueryBuilders.wildcardQuery("title","*小汉堡*"));
        //5.ids 多个指定 id 查询
        //query(QueryBuilders.idsQuery().addIds("1").addIds("2"));
        //6.multi_match 多字段查询
        query(QueryBuilders.multiMatchQuery("豆腐", "title", "description"));
    

    /**
     * 通用查询
     *
     * @param queryBuilder
     * @throws IOException
     */
    public SearchHit[] query(QueryBuilder queryBuilder) throws IOException 
        SearchRequest searchRequest = new SearchRequest("products");//指定搜索索引
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//指定条件对象
        sourceBuilder.query(queryBuilder);//指定查询条件
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//参数 1:搜索请求对象 参数2: 请求配置对象 返回值:查询结果对象
        SearchHit[] hits = searchResponse.getHits().getHits();
        return hits;
    

这是小编在开发学习使用和总结,  这中间或许也存在着不足,希望可以得到大家的理解和建议。如有侵权联系小编!

以上是关于BOOT 整合 ElasticSearch的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot整合Elasticsearch

Elasticsearch学习 spring boot整合Elasticsearch的原生方式

Spring-Boot整合Elasticsearch

Spring Boot整合ElasticSearch和Mysql 附案例源码

Spring Boot 整合 ElasticSearch 框架

Spring Boot整合ElasticSearch实现增删改查基本示例