leyou商城day6 ES介绍及基本操作

16 篇文章 0 订阅
6 篇文章 0 订阅

01、课程目标

  • 掌握Kibana的基本操作
  • 掌握SpringDataElasticsearch常用操作

02、Elasticsearch准备:ElasticSearch工具安装

1)安装ElasticSearch服务器

在这里插入图片描述

把 elasticsearch-6.6.2.zip 解压到没有中文的路径下,然后再把ik插件解压缩,拷贝到es的plugins目录下,启动前,可以修改es的config目录下的jvm.options文件,修改内存大小:默认值是1g,我们改小点,128m或者256m都可以,如果你的电脑内存够,不改也行。

链接:https://pan.baidu.com/s/1TUuc-mfnXS-QjA1QiPUmYQ
提取码:dqol

链接:https://pan.baidu.com/s/1weqAj2UzqjdCjlJgOGbCXw
提取码:agao

在这里插入图片描述

2)安装ik分词器

 

 1598927151060

把以上目录解压到ES的plugins目录下即可。

3)安装head插件

链接:https://pan.baidu.com/s/1V6jx_Z6efV9IC1Ps6SYy2Q
提取码:1pki

head插件有安装版、tomcat版和chrome的插件版,我们采用插件版即可。

在谷歌中按照head插件的流程如下图所示:

在这里插入图片描述

4)安装kibana

什么是Kibana? logstash + Elasticsearch + Kibana ELK 日志监听系统

在这里插入图片描述

Kibana是一个基于Node.js的Elasticsearch索引库数据统计工具,可以利用Elasticsearch的聚合功能,生成各种图表,如柱形图,线状图,饼图等。

而且还提供了操作Elasticsearch索引数据的控制台,并且提供了一定的API提示,非常有利于我们学习Elasticsearch的语法。

Kibana的安装

启动kibana的前提是,需要安装好nodejs的环境。

解压缩就可以使用了,双击解压后的bin目录的kibana.bat文件即可。

链接:https://pan.baidu.com/s/1qi4i0oAwtdrLIkM6NIbmhw
提取码:eu8j

发现kibana的监听端口是5601

我们访问:http://127.0.0.1:5601

5)Elasticsearch API

Elasticsearch提供了Rest风格的API,即http请求接口,也提供了各种语言的客户端API

Rest风格API

文档地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

 1600392183731

各种语言客户端API

Elasticsearch支持的客户端非常多:https://www.elastic.co/guide/en/elasticsearch/client/index.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EywisybG-1656921150371)(assets/1528613714338.png)]

点击Java Rest Client后,你会发现又有两个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZf0Gput-1656921150371)(assets/1528613788606.png)]

Low Level Rest Client是低级别封装,提供一些基础功能,但更灵活

High Level Rest Client,是在Low Level Rest Client基础上进行的高级别封装,功能更丰富和完善,而且API会变的简单

在这里插入图片描述

今天的学习思路,我们先补充ES的REST风格API,然后再学习SpringDataElasticsearch(对Java语言客户端API的封装)

03、Elasticsearch准备:搜索相关概念回顾

1)结构回顾

索引、类型、文档、域

在这里插入图片描述

2)文档对象的域字段需要设置的三个属性

是否索引
索引的字段才能被搜索到,不索引,就意味着不被搜索匹配。
日期,数量,点击数不索引。
是否分词
分词就意味着要先分词,再拿分词后的词条匹配搜索结果。
如果不分词,就直接把当前搜索的内容当作一个词条。
人名,地名都不分词。
是否存储
存储就意味着要显示,不存储不显示。
但是如果使用了elasticSearch,默认是所有字段全部存储。
被标记为store=true的是存储在索引库【lucene定义的】中。
如果没有store=true标记,存储在elasticSearch自己的库中。

这些三部分内容我们都可以通过mapping来定义:

Mapping是用来定义Document中每个Field的特征的(数据类型,是否存储,是否索引,是否分词等)

类型名称:就是前面将的type的概念,类似于数据库中的表
字段名:任意填写,下面指定许多属性,例如:

- type:类型,可以是text、long、short、date、integer、object等
- index:是否索引,默认为true
- store:是否存储,默认为false
- analyzer:分词器,这里的ik_max_word即使用ik分词器

04、ES的REST风格API:基本CRUD(了解)

接下来我们先学习ES的REST风格API如何操作ES。

1)创建索引

Elasticsearch采用Rest风格API,因此其API就是一次http请求,你可以用任何工具发起http请求

创建索引的请求格式:

  • 请求方式:PUT

  • 请求路径:/索引库名

  • 请求参数:json格式:

    {
        "settings": {
            "number_of_shards": 3,
            "number_of_replicas": 2
          }
    }
    
    • settings:索引库的设置
      • number_of_shards:分片数量
      • number_of_replicas:副本数量

kibana的控制台,可以对http请求进行简化,示例:

创建goods索引:

在这里插入图片描述

相当于是省去了elasticsearch的服务器地址

而且还有语法提示,非常舒服。

2)删除索引

删除索引使用DELETE请求

语法

DELETE /索引库名

示例

在这里插入图片描述

查看goods索引库是否存在:

在这里插入图片描述

当然,我们也可以用HEAD请求,查看索引是否存在:

在这里插入图片描述

3)新增数据

随机生成id

通过POST请求,可以向一个已经存在的索引库中添加数据。

语法:

POST /索引库名/类型名
{
    "key":"value"
}

示例:

POST /goods/docs
{
     "title":"小米手机",
    "images":"http://image.leyou.com/12479122.jpg",
    "price":2699.00
}

响应:

{
  "_index" : "goods",
  "_type" : "docs",
  "_id" : "QGGcR3QBMpeF1wVyBCzk",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

通过kibana查看数据:

GET /goods/_search
{
  "query": {
    "match_all": {
      
    }
  }
}
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "QGGcR3QBMpeF1wVyBCzk",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.leyou.com/12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

  • _source:源文档信息,所有的数据都在里面。
  • _id:这条文档的唯一标示,与文档自己的id字段没有关联
自定义id

如果我们想要自己新增的时候指定id,可以这么做:

POST /索引库名/类型/id值
{
    ...
}

示例:

POST /goods/docs/2
{
    "title":"大米手机",
    "images":"http://image.leyou.com/12479122.jpg",
    "price":2899.00
}

得到的数据:

{
  "_index": "goods",
  "_type": "docs",
  "_id": "2",
  "_score": 1,
  "_source": {
    "title": "大米手机",
    "images": "http://image.leyou.com/12479122.jpg",
    "price": 2899
  }
}

4)修改数据

把刚才新增的请求方式改为PUT,就是修改了。不过修改必须指定id,

  • id对应文档存在,则修改
  • id对应文档不存在,则新增

比如,我们把id为3的数据进行修改:

PUT /goods/docs/2
{
    "title":"超大米手机",
    "images":"http://image.leyou.com/12479122.jpg",
    "price":3899.00,
    "stock": 100,
    "saleable":true
}

结果:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "超大米手机",
          "images" : "http://image.leyou.com/12479122.jpg",
          "price" : 3899.0,
          "stock" : 100,
          "saleable" : true
        }
      }
    ]
  }
}

5)删除数据

删除使用DELETE请求,同样,需要根据id进行删除:

语法

DELETE /索引库名/类型名/id值

示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvooLjYX-1656921150374)(assets/1598929667944.png)]

# 创建索引库
PUT /goods
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
}

# 删除索引库
DELETE /goods

#插入文档

# 随机生成文档ID
POST /goods/docs
{
   "title":"小米手机",
    "images":"http://image.leyou.com/12479122.jpg",
    "price":2699.00
}

# 自行赋值文档ID
POST /goods/docs/2
{
   "title":"华为手机",
    "images":"http://image.leyou.com/12479123.jpg",
    "price":3699.00
}

# 修改文档(修改时必须有文档ID)
PUT /goods/docs/2
{
   "title":"华为手机",
    "images":"http://image.leyou.com/12479123.jpg",
    "price":4699.00
}

# 删除文档
DELETE /goods/docs/2





05、ES的REST风格API:基本查询(了解)

1)查询基本语法

基本语法

GET /索引库名/_search
{
    "query":{
        "查询类型":{
            "查询条件":"查询条件值"
        }
    }
}

这里的query代表一个查询对象,里面可以有不同的查询属性

  • 查询类型:
    • 例如:match_allmatchtermrange 等等
  • 查询条件:查询条件会根据类型的不同,写法也有差异,后面详细讲解

导入测试数据:

PUT /goods
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}

POST /goods/docs/1
{
  "title":"小米Redmi K30",
  "subTitle":"小米手机 120Hz流速屏,王一博同款火爆开售",
  "category":"手机",
  "price": 1500
}

POST /goods/docs/2
{
  "title":"小米Redmi 8A",
  "subTitle":"小米手机 5000mAh大电量,支持18W快充,Type-C充电接口,6.22英寸水滴屏",
  "category":"手机",
  "price": 700
}

POST /goods/docs/3
{
  "title":"小米电视",
  "subTitle":"高清教育互联",
  "category":"电视",
  "price": 3500
}

POST /goods/docs/4
{
  "title":"华为P90",
  "subTitle":"华为P90 高端大气上档次",
  "category":"手机",
  "price":2500
}

POST /goods/docs/5
{
  "title":"小米电视P10",
  "subTitle":"小米电视P10 高端大气上档次",
  "category":"电视",
  "price":2900
}

2)查询所有(match_all)

示例:

GET /goods/_search
{
    "query":{
        "match_all": {}
    }
}
  • query:代表查询对象
  • match_all:代表查询所有

结果:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "QGGcR3QBMpeF1wVyBCzk",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.leyou.com/12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

  • took:查询花费时间,单位是毫秒
  • time_out:是否超时
  • _shards:分片信息
  • hits:搜索结果总览对象
    • total:搜索到的总条数
    • max_score:所有结果中文档得分的最高分
    • hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
      • _index:索引库
      • _type:文档类型
      • _id:文档id
      • _score:文档得分
      • _source:文档的源数据

3)匹配查询(match)

匹配查询(会先分词再匹配):

GET /goods/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  }
}

结果:

{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.1507283,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "QGGcR3QBMpeF1wVyBCzk",
        "_score" : 1.1507283,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.leyou.com/12479122.jpg",
          "price" : 2699.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "3",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "小米电视4A",
          "images" : "http://image.leyou.com/12479122.jpg",
          "price" : 3899.0
        }
      }
    ]
  }
}

在上面的案例中,不仅会查询到电视,而且与小米相关的都会查询到,多个词之间是or的关系。

  • and关系

某些情况下,我们需要更精确查找,我们希望这个关系变成and,可以这样做:

GET /heima/_search
{
    "query":{
        "match": {
          "title": {
            "query": "小米电视",
            "operator": "and"
          }
        }
    }
}

结果:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 3,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.5753642,
    "hits": [
      {
        "_index": "heima",
        "_type": "goods",
        "_id": "3",
        "_score": 0.5753642,
        "_source": {
          "title": "小米电视4A",
          "images": "http://image.leyou.com/12479122.jpg",
          "price": 3899
        }
      }
    ]
  }
}

本例中,只有同时包含小米电视的词条才会被搜索到。

4)多字段查询(multi_match)

multi_matchmatch类似,不同的是它可以在多个字段中查询

GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "小米高端大气",
      "fields": ["title","subTitle"]
    }
  }
}

# 查询全部
GET /goods/_search
{
  "query": {
    "match_all": {}
  }
}

# 匹配查询(match ,先分词再查询)
GET /goods/_search
{
  "query": {
    "match": {
      "title": "小米电视"
    }
  }
}

# 匹配查询(match + or/and )

GET /goods/_search
{
  "query": {
    "match": {
      "title": {
        "query": "小米电视",
        "operator": "and"
      }
    }
  }
}


# 多字段匹配(multi_match)
GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "小米",
      "fields": ["title","subTitle"]
    }
  }
}




5)词条匹配(term)

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串

GET /goods/_search
{
  "query": {
    "term": {
      "price": {
        "value": 2500
      }
    }
  }
}

结果:

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "title" : "华为P90",
          "subTitle" : "华为P90 高端大气上档次",
          "category" : "手机",
          "price" : 2500
        }
      }
    ]
  }
}

6)多词条精确匹配(terms)

terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:

GET /goods/_search
{
  "query": {
    "terms": {
      "price": [
        4899.0,
        2699.0
      ]
    }
  }
}

结果:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "title" : "华为手机",
          "subTitle" : "小米",
          "images" : "http://image.leyou.com/12479124.jpg",
          "price" : 4899.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "QGGcR3QBMpeF1wVyBCzk",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.leyou.com/12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

7)结果过滤(_source)

默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source的所有字段都返回。

如果我们只想获取其中的部分字段,我们可以添加_source的过滤

直接指定字段

示例:

GET /goods/_search
{
  "query": {
    "term": {
      "price": 4899.0
    }
  },
  "_source": ["title","price"]
}

返回的结果:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "price" : 4899.0,
          "title" : "华为手机"
        }
      }
    ]
  }
}
指定includes和excludes

我们也可以通过:

  • includes:来指定想要显示的字段
  • excludes:来指定不想要显示的字段

二者都是可选的。

includes示例:

GET /goods/_search
{
  "query": {
    "term": {
      "price": 4899.0
    }
  },
  "_source":{
    "includes": ["title","subTitle"]
  }
}

excludes示例:

GET /goods/_search
{
  "query": {
    "term": {
      "price": 4899.0
    }
  },
  "_source":{
    "excludes": ["images"]
  }
}

8)布尔组合(bool)

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

GET /goods/_search
{
  "query": {
     "bool": {
       "must": [
         {
           "match": {
             "title": "小米"
           }
         },
         {
           "match": {
             "subTitle": "小米"
           }
         }
       ]
     }
  }
}


结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.18232156,
    "hits" : [
      {
        "_index" : "item",
        "_type" : "docs",
        "_id" : "2",
        "_score" : 0.18232156,
        "_source" : {
          "id" : 2,
          "title" : "坚果手机R1",
          "category" : "手机",
          "brand" : "锤子",
          "price" : 3699.0,
          "images" : "http://image.leyou.com/123.jpg"
        }
      },
      {
        "_index" : "item",
        "_type" : "docs",
        "_id" : "3",
        "_score" : 0.18232156,
        "_source" : {
          "id" : 3,
          "title" : "华为META10",
          "category" : "手机",
          "brand" : "华为",
          "price" : 4499.0,
          "images" : "http://image.leyou.com/3.jpg"
        }
      }
    ]
  }
}

9)过滤条件(filter)

所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter方式:

注意:filter通常和bool一起使用(在bool基础上进行过滤)。

GET /goods/_search
{
  "query": {
     "bool": {
       "must": [
         {
           "match": {
             "title": "小米"
           }
         },
         {
           "match": {
             "subTitle": "小米"
           }
         }
       ],
       "filter": {
         "term": {
           "category.keyword": "手机"
         }
       }
     }
  }
}


# 查询所有
GET /goods/_search
{
  "query": {
    "match_all": {}
  }
}

# 匹配查询(模糊查询)(先分词再查询)
GET /goods/_search
{
  "query": {
    "match": {
      "title": "小米"
    }
  }
}

# 多字段匹配查询(模糊查询)
GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "小米",
      "fields": ["title","subTitle"]
    }
  }
}


# 词条搜素(精确搜素)(不分词搜素)
GET /goods/_search
{
  "query": {
     "term": {
       "price": {
         "value": 700
       }
     }
  }
}

# 多词条搜索
GET /goods/_search
{
  "query": {
     "terms": {
       "price": [
        700,
        2500
       ]
     }
  }
}

# 结果过滤
# 语法1
GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title","price"]
}
# 语法2
GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "_source":{
    "excludes": ["subTitle"]
  }
}

# 布尔组合
# 把分词的Field转换为不分词的Field: filed.keyword

# 使用布尔的条件
GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "小米"
          }
        },
        {
            "term": {
              "category.keyword": {
                "value": "手机"
              }
            }
        }
      ]
    }
  }
}



# 使用布尔的过滤
# 注意:如果我们使用过滤条件,那么过滤后的结果不会影响原来结果的排序。反之,如果不用过滤,则会影响原来结果排序

GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "小米"
          }
        }
      ],
      "filter":{
        "term": {
          "category.keyword": "手机"
        }
      }
    }
  }
}





06、ES的REST风格API:聚合查询(aggregations)(了解)

聚合查询就是分组统计

聚合可以让我们极其方便的实现对数据的统计、分析。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。

1)聚合基本概念

Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫,一个叫度量

桶(bucket)

桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个,例如我们根据国籍对人划分,可以得到中国桶英国桶日本桶……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。

Elasticsearch中提供的划分桶的方式有很多:

  • Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
  • Histogram Aggregation:根据数值阶梯分组,与日期类似
  • Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
  • Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
  • ……

bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量

度量(metrics)

分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量

比较常用的一些度量聚合方式:

  • Avg Aggregation:求平均值
  • Max Aggregation:求最大值
  • Min Aggregation:求最小值
  • Percentiles Aggregation:求百分比
  • Stats Aggregation:同时返回avg、max、min、sum、count等
  • Sum Aggregation:求和
  • Top hits Aggregation:求前几
  • Value Count Aggregation:求总数
  • ……

为了测试聚合,我们先批量导入一些数据

创建索引:

PUT /cars
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "transactions": {
      "properties": {
        "color": {
          "type": "keyword"
        },
        "make": {
          "type": "keyword"
        }
      }
    }
  }
}

注意:在ES中,需要进行聚合、排序、过滤的字段其处理方式比较特殊,因此不能被分词。这里我们将color和make这两个文字类型的字段设置为keyword类型,这个类型不会被分词,将来就可以参与聚合

导入数据

POST /cars/transactions/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

2)聚合为桶 (Aggragation for Buket)

聚合为桶其实就是分组统计!

首先,我们按照 汽车的颜色color来划分

GET /cars/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            }
        }
    }
}
  • size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
  • aggs:声明这是一个聚合查询,是aggregations的缩写
    • popular_colors:给这次聚合起一个名字,任意。
      • terms:划分桶的方式,这里是根据词条划分
        • field:划分桶的字段

结果:

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 8,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "popular_colors": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "red",
          "doc_count": 4
        },
        {
          "key": "blue",
          "doc_count": 2
        },
        {
          "key": "green",
          "doc_count": 2
        }
      ]
    }
  }
}
  • hits:查询结果为空,因为我们设置了size为0
  • aggregations:聚合的结果
  • popular_colors:我们定义的聚合名称
  • buckets:查找到的桶,每个不同的color字段值都会形成一个桶
    • key:这个桶对应的color字段的值
    • doc_count:这个桶中的文档数量

通过聚合的结果我们发现,目前红色的小车比较畅销!

# 聚合查询(分组统计)

# AGG_TYPE: 聚合类型

# 常见的聚合类型
# terms: 按数量聚合(统计),类似SQL的count(*)
# avg: 按均值聚合(统计),类似SQL的avg(age)
# max: 按最大值聚合(统计),类似SQL的max(age)
# min: 按最小聚合(统计),类似SQL的min(age)
# sum: 按求和聚合(统计),类似SQL的sum(score)

#统计不同颜色的汽车分别有多少部?

GET /cars/_search
{
 "aggs": {
   "colorAgg": {
     "terms": {
       "field": "color"
     }
   }
 } 
  
}

# 统计所有汽车的均价

GET /cars/_search
{
 "aggs": {
   "priceAgg": {
     "avg": {
       "field": "price"
     }
   }
 }
  
}

#查询honda的汽车的均价?

GET /cars/_search
{
  "query": {
    "term": {
      "make": {
        "value": "honda"
      }
    }
  },
  "aggs": {
   "priceAgg": {
     "avg": {
       "field": "price"
     }
   }
 }
  
}


# 统计不同颜色的汽车的均价分别是多少?

GET /cars/_search
{
 "aggs": {
   "colorAgg": {
     "terms": {
       "field": "color"
     },
     "aggs": {
       "priceAgg": {
         "avg": {
           "field": "price"
         }
       }
     }
   }
 } 
  
}



07、SpringDataElasticsearch:搭建项目(重点)

1)SpringDataElasticsearch简介

Spring Data Elasticsearch是Spring Data项目下的一个子模块。

查看 Spring Data的官网:http://projects.spring.io/spring-data/

在这里插入图片描述

Spring Data的使命是为数据访问提供熟悉且一致的基于Spring的编程模型,同时仍保留底层数据存储的特殊特性。

它使得使用数据访问技术,关系数据库和非关系数据库,map-reduce框架和基于云的数据服务变得容易。这是一个总括项目,其中包含许多特定于给定数据库的子项目。这些令人兴奋的技术项目背后,是由许多公司和开发人员合作开发的。

Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。

包含很多不同数据操作的模块:

 1598941429101

Spring Data Elasticsearch的页面:https://projects.spring.io/spring-data-elasticsearch/

 在这里插入图片描述

特征:

  • 支持Spring的基于@Configuration的java配置方式,或者XML配置方式
  • 提供了用于操作ES的便捷工具类**ElasticsearchTemplate**。包括实现文档到POJO之间的自动智能映射。
  • 利用Spring的数据转换服务实现的功能丰富的对象映射
  • 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式
  • 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询

2)搭建SpringDataElasticsearch环境

创建项目及导入依赖

我们使用spring脚手架新建一个demo,学习Elasticsearch

 在这里插入图片描述

pom依赖:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>spring-data-es</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!--spring data es-->
        <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>
        </dependency>
    </dependencies>
    
</project>
application.yml配置
spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch # 集群名称
      cluster-nodes: 127.0.0.1:9300 # 节点地址
编写启动类
package com.itheima;

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

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



实体类及注解

首先我们准备好实体类:

public class Item {
    Long id;
    String title; //标题
    String category;// 分类
    String brand; // 品牌
    Double price; // 价格
    String images; // 图片地址
}

映射

Spring Data通过注解来声明字段的映射属性,有下面的三个注解:

  • @Document 作用在类,标记实体类为文档对象,一般有四个属性
    • indexName:对应索引库名称
    • type:对应在索引库中的类型
    • shards:分片数量,默认5
    • replicas:副本数量,默认1
  • @Id 作用在成员变量,标记一个字段作为id主键
  • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
    • type:字段类型,取值是枚举:FieldType
    • index:是否索引,布尔类型,默认是true
    • store:是否存储,布尔类型,默认是false
    • analyzer:分词器名称:ik_max_word

示例:

package com.itheima.pojo;

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;

/**
 *  @Document: 映射索引库中的文档
 *    indexName: 索引库名称
 *    type: 类型
 *    shards:分片
 *    replicas:副本
 *  @Id: 映射文档ID
 *  @Field
 *    type: 字段的类型
 *         Text: 文本类型,分词的
 *         Keyword: 文本类型,不分词的
 *         Integer、Long、Float、Double: 数值类型,必须不分词的
 *         Boolean:布尔类型,必须不分词的
 *         Date: 日期类型,必须不分词的
 *         Object: 自定义对象或集合(List,Set,Map等),所有对象里面的属性都是索引和分词的
 *    index:
 *         该字段是否索引  默认为true
 *    analyzer
 *         指定分词器的算法
 *            如:ik分词器的算法
 *                 ik_smart: 最小分词    我是程序员 ->  我  是  程序员
 *                 ik_max_word: 最细分词   我是程序员 ->  我  是  程序员  程序 员
 */
@Data
@Document(indexName = "goods",type = "docs" ,shards = 1,replicas = 1)
public class Item {
    @Id
    Long id;

    @Field(type = FieldType.Text,analyzer ="ik_max_word" )
    String title; //标题
    
    @Field(type = FieldType.Keyword)
    String category;// 分类
    @Field(type = FieldType.Keyword)
    String brand; // 品牌
    @Field(type = FieldType.Double)
    Double price; // 价格
    @Field(type = FieldType.Keyword,index = false)
    String images; // 图片地址
}


08、SpringDataElasticsearch:基本CRUD(重点)

1)编写Repository接口

Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。

我们只需要定义接口,然后继承它就OK了。

在这里插入图片描述

package com.ithiema.repository;

import com.ithiema.pojo.Item;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/**
 * Dao接口
 * 泛型一:操作的实体类类型
 * 泛型二:实体类的ID类型
 */
public interface ItemRepository extends ElasticsearchRepository<Item,Long>{
}


来看下Repository的继承关系:

在这里插入图片描述

我们看到有一个ElasticsearchRepository接口:
在这里插入图片描述

2)新增文档

package com.itcast;

import com.itcast.pojo.Item;
import com.itcast.repository.ItemRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class EsDemo1 {
    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void save(){
        Item item = new Item(1L, "小米手机7", "手机",
                "小米", 3499.00, "http://image.leyou.com/13123.jpg");
        itemRepository.save(item);
    }

}


去页面查询看看:

GET /goods/_search

结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "docs",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "id" : 1,
          "title" : "小米手机7",
          "category" : "手机",
          "brand" : "小米",
          "price" : 3499.0,
          "images" : "http://image.leyou.com/13123.jpg"
        }
      }
    ]
  }
}

3)批量新增

代码:

   /**
  * 批量新增索引
  */
@Test
    public void testBatchSave(){
        List<Item> list = new ArrayList<>();
        list.add(new Item(2L, "坚果手机R1", "手机","锤子", 3699.00, "http://image.leyou.com/13124.jpg"));
        list.add(new Item(3L, "华为META10", "手机","华为", 4499.00, "http://image.leyou.com/13125.jpg"));
        list.add(new Item(4L, "小米电视1", "电视","小米", 5499.00, "http://image.leyou.com/13123.jpg"));
        list.add(new Item(5L, "小米手机8", "手机","小米", 2199.00, "http://image.leyou.com/13124.jpg"));
        itemRepository.saveAll(list);
    }

4)修改文档

修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。

/**
 * 修改索引
 */
@Test
public void testUpdate(){
    Item item = new Item(1L, "小米手机8", "手机",
            "小米", 4499.00, "http://image.leyou.com/13124.jpg");
    itemRepository.save(item);
}

5)简单查询

ElasticsearchRepository提供了一些基本的查询方法:

在这里插入图片描述

我们来试试查询所有:

  /**
     * 查询所有
     */
    @Test
    public void testFindAll(){
        Iterable<Item> list = itemRepository.findAll();
        list.forEach(System.out::println);
    }
    /**
     * 根据id查询
     */
    @Test
    public void testFindById(){
        Item item = itemRepository.findById(1L).get();
        System.out.println(item);
    }

结果:

在这里插入图片描述

6)删除文档

/**
 * 删除索引
 */
@Test
public void testDelete(){
    Item item = new Item();
    item.setId(1L);
    itemRepository.delete(item);
}
package com.itheima;

import com.itheima.pojo.Item;
import com.itheima.repository.ItemRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;

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

/***
 * 基本CRUD
 */
//@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class) //加载启动类
public class ESDemo1 {

    @Autowired
    private ItemRepository itemRepository;

    /**
     * 增加、修改
     */
    @Test
    public void testSave(){
        Item item = new Item(1L, "小米手机7", "手机",
                "小米", 3499.00, "http://image.leyou.com/13123.jpg");
        itemRepository.save(item);
    }

    /**
     * 批量增加
     */
    @Test
    public void testSaveAll(){
        List<Item> list = new ArrayList<>();
        list.add(new Item(2L, "坚果手机R1", "手机","锤子", 3699.00, "http://image.leyou.com/13124.jpg"));
        list.add(new Item(3L, "华为META10", "手机","华为", 4499.00, "http://image.leyou.com/13125.jpg"));
        list.add(new Item(4L, "小米电视1", "电视","小米", 5499.00, "http://image.leyou.com/13123.jpg"));
        list.add(new Item(5L, "小米手机8", "手机","小米", 2199.00, "http://image.leyou.com/13124.jpg"));
        itemRepository.saveAll(list);
    }

    /**
     * 查询所有
     */
    @Test
    public void testFindAll(){
        Iterable<Item> it = itemRepository.findAll();
        for(Item item:it){
            System.out.println(item);
        }
    }

    @Test
    public void testFindById(){
        Item item = itemRepository.findById(3L).get();
        System.out.println(item);
    }

    @Test
    public void testDeleteById(){
        itemRepository.deleteById(2L);
    }
}


09、SpringDataElasticsearch:高级查询(重点)

如果要完成更加复杂的查询,同时包含条件,分页,高亮,聚合等操作,需要用到ElasticsearchTemplate对象

1)基本条件查询

# 基本查询
GET /goods/_search
{
  "query": {
    "match": {
      "title": "小米"
    }
  }
}


@Test
    public void test1(){
        //1.创建本地查询构造器对象: 用于封装所有查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //添加query条件
        //注意:所有的Query条件都是使用QueryBuilders类构造的
        queryBuilder.withQuery(QueryBuilders.matchQuery("title","小米"));


        //2.执行查询(执行本地查询),获取结果
        /**
         * 参数一:本地查询对象
         * 参数二:需要封装数据的实体类
         */
        List<Item> items = esTemplate.queryForList(queryBuilder.build(), Item.class);

        //3.处理结果
        items.forEach(System.out::println);
    }

2)分页查询

# 分页
# from: 起始索引行号,从0开始
# size: 页面大小
GET /goods/_search
{
  "query": {
    "match": {
      "title": "小米"
    }
  },
  "from": 0 ,
  "size": 2
}


 /**
     * 2)分页条件查询
     */
    @Test
    public void testPageQuery(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1. 添加Query条件
        queryBuilder.withQuery( QueryBuilders.matchQuery("title","小米") );


        //实际页面传递的参数
        int page = 1;//页面传递的当前页码
        int size = 2;//页面大小


        //2.添加分页参数
        /**
         * page: ES的当前页码(page从0开始计算)
         * size: 页面大小
         */
        queryBuilder.withPageable(PageRequest.of(page-1,size));

        //Page: 用于封装分页查询结果
        Page<Item> pageBean = esTemplate.queryForPage(queryBuilder.build(),Item.class);

        System.out.println("总记录数:"+pageBean.getTotalElements());
        System.out.println("总页数:"+pageBean.getTotalPages());
        pageBean.getContent().forEach(System.out::println);
    }

3)布尔组合+过滤查询(*)

# 布尔+过滤

GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "小米"
          }
        }
      ],
      "filter": {
        "term": {
          "category": "手机"
        }
      }
    }
  }
}
  /**
     * 3)布尔+过滤
     */
    @Test
    public void testBoolQueryAndFilter(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1.1 添加Query条件
        //1)创建布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //2)往布尔查询中添加must条件
        boolQueryBuilder.must( QueryBuilders.matchQuery("title","小米")  );
        //3)往布尔查询中添加filter过滤
        boolQueryBuilder.filter( QueryBuilders.termQuery("category","手机") );

        queryBuilder.withQuery( boolQueryBuilder );


        List<Item> items = esTemplate.queryForList(queryBuilder.build(), Item.class);

        items.forEach(System.out::println);

    }

4)聚合查询(*)

# 统计不同品牌的手机分别有多少台?
# 聚合查询

GET /goods/_search
{
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand"
      }
    }
  }
}



 /**
     * 4)聚合查询
     */
    @Test
    public void testAggQuery(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1.添加聚合条件
        //注意:聚合的条件都是由AggrationBuilders构建而来的
        queryBuilder.addAggregation( AggregationBuilders.terms("brandAgg").field("brand")  );

        //注意:执行聚合查询必须使用queryForPage方法,否则无法获取聚合结果

        //AggregatedPage是Page的子类,Page只是分页查询结果,AggregatedPage既封装了分页结果,也封装了聚合查询
        AggregatedPage<Item> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(),Item.class);

        //只取出聚合结果
        Aggregations aggregations = aggregatedPage.getAggregations();

        //根据聚合别名取出聚合结果
        Terms terms = aggregations.get("brandAgg");

        //取出所有Bucket
        List<? extends Terms.Bucket> buckets = terms.getBuckets();

        //遍历Bucket
        for(Terms.Bucket bucket:buckets){
            String brandName = bucket.getKeyAsString();
            long count = bucket.getDocCount();
            System.out.println(brandName+"\t"+count);
        }
    }
    /**
     * 4)聚合查询取出聚合查询里边的的数据  进行2次分组
     */
    @Test
    public void testAggQuery2(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1.添加聚合条件
        //注意:聚合的条件都是由AggregationBuilders构建而来的
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAggs").field("brand")
                .subAggregation(AggregationBuilders.terms("priceAggs").field("price")));
        //注意:执行聚合查询必须使用queryForPage方法,否则无法获取聚合结果
        AggregatedPage<Item> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Item.class);
        //AggregatedPage是Page的子类,Page只是分页查询结果,AggregatedPage既封装了分页结果,也封装了聚合查询
        //只取出聚合结果
        Aggregations aggregations = aggregatedPage.getAggregations();
        //根据聚合别名取出聚合结果
        Terms terms = aggregations.get("brandAggs");
        //取出所有Bucket
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        //遍历Bucket
        for (Terms.Bucket bucket : buckets) {
            String brandName = bucket.getKeyAsString();
            long count = bucket.getDocCount();
            Aggregations aggregations1 = bucket.getAggregations();
            Terms priceAggs = aggregations1.get("priceAggs");
            List<? extends Terms.Bucket> buckets1 = priceAggs.getBuckets();
            Terms.Bucket bucket1 = buckets1.get(0);
            System.out.println(brandName+"\t"+count+"\t"+bucket1.getKey());
        }

    }

5)高亮查询(*)

DSL语句:

# 高亮显示

# 注意:如果要某字段高亮显示,该字段必须是参与搜索,且可以分词

GET /goods/_search
{
   "query": {
     "match": {
       "title": "小米"
     }
   },
   "highlight": {
     "pre_tags": "<font color='red'>",
     "post_tags": "</font>",
     "fields": {
       "title": {}
     }
   }
}


/**
     * 5)高亮查询
     */
    @Test
    public void testHighlightQuery(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1.添加Query条件(title要高亮,所以title参与搜索)
        queryBuilder.withQuery( QueryBuilders.matchQuery("title","小米") );

        //2.添加highlight高亮字段
        HighlightBuilder.Field field = new HighlightBuilder.Field("title");
        //设置前缀和后缀
        field.preTags("<font color='red'>");
        field.postTags("</font>");
        queryBuilder.withHighlightFields(field);

        //3.执行查询
        //SearchResultMapper: 用于自行封装搜索结果
        AggregatedPage<Item> pageBean = esTemplate.queryForPage(queryBuilder.build(), Item.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> list = new ArrayList<>();

                SearchHits hits = response.getHits();
                for(SearchHit hit:hits){
                    //取出原来的文档内容
                    String json = hit.getSourceAsString();
                    //把json字符串转换对象
                    Item item = JsonUtils.toBean(json, Item.class);

                    //自行取出高亮title的内容
                    HighlightField titleField = hit.getHighlightFields().get("title");
                    if(titleField!=null){
                        //赋值给Item对象的title
                        item.setTitle(titleField.getFragments()[0].toString());
                    }

                    list.add((T)item);
                }

                AggregatedPage<T> aggregatedPage = new AggregatedPageImpl<>(list);
                return aggregatedPage;
            }
        });

        pageBean.getContent().forEach(System.out::println);
    }

结果为:

Item(id=3, title=<font color='red'>华为</font>META10, category= 手机, brand=华为, price=4499.0, images=http://image.leyou.com/3.jpg)

加入HighlightUtils工具类,简化后:

  /**
     * 5)高亮查询 - 使用工具类
     */
    @Test
    public void testHighlightQuery2(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1.添加Query条件(title要高亮,所以title参与搜索)
        queryBuilder.withQuery( QueryBuilders.matchQuery("title","小米") );

        //2.添加highlight高亮字段
        HighlightUtils.highlightField(queryBuilder,"title");

        //3.执行查询
        //SearchResultMapper: 用于自行封装搜索结果
        AggregatedPage<Item> pageBean = esTemplate.queryForPage(
                queryBuilder.build(),
                Item.class,
                HighlightUtils.highlightBody(Item.class,"title"));

        pageBean.getContent().forEach(System.out::println);
    }

​ …

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值