学习笔记--Elasticsearch

  • Elasticsearch 是一个开源的高扩展的分布式全文搜索引擎,是整个Elastic Stack技术栈的核心。它可以近乎实时的存储,检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
  • 分布式,无需人工搭建集群(solr就需要人为配置,使用Zookeeper作为注册中心)
  • Restful风格,一切API都遵循Rest原则,容易上手
  • 近实时搜索,数据更新在Elasticsearch中几乎是完全同步的
  • 9300:集群节点间通讯接口
  • 9200:客户端访问接口
  • 用途
    1. 搜索的数据对象是大量的非结构化的文本数据。
    2. 文件记录达到数十万或数百万个甚至更多。
    3. 支持大量基于交互式文本的查询。
    4. 需求非常灵活的全文搜索查询。
    5. 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
    6. 对不同记录类型,非文本数据操作或安全事务处理的需求相对较少的情况。

es下载安装

官方网站:https://www.elastic.co/cn/elasticsearch/

中文社区:https://elasticsearch.cn/explore/

  • 因为有安全方面的考虑,es是不能在root账户下运行的,安装时首先需要切换账户
  • 将下载的安装包进行解压
  • 解压后进入目录,然后进入config目录,有俩个需要修改的配置文件

jvm.options(Elasticsearch基于Lucene的,而Lucene底层是java实现,因此我们需要配置jvm参数):

找到下边的位置

-Xms1g
-Xmx1g

此为默认设置,可以将他设置的大一点或小一点,例如修改为

-Xms512m
-Xmx512m	

elasticsearch.yml:

修改数据和日志目录

path.data: /home/leyou/elasticsearch/data # 数据目录位置,没有此文件夹的话就创建一个
path.logs: /home/leyou/elasticsearch/logs # 日志目录位置,没有此文件夹的话就创建一个

修改绑定的ip(默认只允许本机访问,修改为0.0.0.0后则可以远程访问)

network.host: 0.0.0.0 # 绑定到0.0.0.0,允许任何ip来访问

一些其他参数:

属性名说明
cluster.name配置elasticsearch的集群名称,默认是elasticsearch。建议修改成一个有意义的名称。
node.name节点名,es会默认随机指定一个名字,建议指定一个有意义的名称,方便管理
path.conf设置配置文件的存储路径,tar或zip包安装默认在es根目录下的config文件夹,rpm安装默认在/etc/ elasticsearch
path.data设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开
path.logs设置日志文件的存储路径,默认是es根目录下的logs文件夹
path.plugins设置插件的存放路径,默认是es根目录下的plugins文件夹
bootstrap.memory_lock设置为true可以锁住ES使用的内存,避免内存进行swap
network.host设置bind_host和publish_host,设置为0.0.0.0允许外网访问
http.port设置对外服务的http端口,默认为9200。
transport.tcp.port集群结点之间通信端口
discovery.zen.ping.timeout设置ES自动发现节点连接超时的时间,默认为3秒,如果网络延迟高可设置大些
discovery.zen.minimum_master_nodes主结点数量的最少值 ,此值的公式为:(master_eligible_nodes / 2) + 1 ,比如:有3个符合要求的主结点,那么这里要设置为2
  • 一些可能遇到的错误(主要争对linux系统,错误修改完毕,要重启你的 Xshell终端才会生效):

1.linux内核过低

image-20221010142359537

在elasticsearch.yml中修改配置:

bootstrap.system_call_filter: false

2.权限不足,Linux中不能用root,切换账户后必须给账户授权image-20221010142601540

在root账户下执行命令:chmod -R 777 /es目录/

或者

在root账户下打开文件:vim /etc/security/limits.conf,添加:

* soft nofile 65536

* hard nofile 131072

* soft nproc 4096

* hard nproc 4096

3.线程数不够:[1]: max number of threads [1024] for user [leyou] is too low, increase to at least [4096]

打开文件:vim /etc/security/limits.d/90-nproc.conf

修改 * soft nproc 1024 为 * soft nproc 4096

4.进程虚拟内存:[3]: max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]

打开文件:vim /etc/sysctl.conf

添加内容:vm.max_map_count=655360

保存退出后执行命令:sysctl -p

Kibana以及ik分词器安装

  • Kibana是es的可视化操作工具,它和es的关系就好像Kibana是Navicat,而es是mysql安装在电脑上的服务。
  • Kibana安装的版本必须和es的版本一致
  • 下载好后进入安装目录下的config目录,修改kibana.yml文件:
elasticsearch.url: "http://安装了es的IP地址:9200"
  • 进入bin目录下找到启动文件点击启动即可,端口为5601:http://127.0.0.1:5601

  • 然后进入控制台操作image-20221010145230230

  • 在https://github.com/medcl/elasticsearch-analysis-ik 下载对应的版本,然后解压缩到es的安装目录中的plugins目录中

  • 在Kibana控制台输入,出现下图即表示安装成功

# 通过ik分词器来分词
POST /_analyze
{
  "analyzer": "ik_smart"
  ,"text": "我是中国人,我热爱我的祖国"
}

image-20221010145412311

基本概念

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。

四个基本:
索引(indices)--------------------------------Databases 数据库

  类型(type)-----------------------------Table 数据表()

     文档(Document)----------------Row 行

	   字段(Field)-------------------Columns 列 
  • 注意:es5版本一个索引有多个类型,es6版本一个索引只有一个类型,es7版本取消类型这个概念

详细说明:

概念说明
索引库(indices)indices是index的复数,代表许多的索引,
类型(type)es5版本一个索引有多个类型,es6版本一个索引只有一个类型,es7版本取消类型这个概念
文档(document)存入索引库原始的数据。比如每一条商品信息,就是一个文档
字段(field)文档中的属性
映射配置(mappings)字段的数据类型、属性、是否索引、是否存储等特性

是不是与Lucene和solr中的概念类似。

另外,在SolrCloud中,有一些集群相关的概念,在Elasticsearch也有类似的:

  • 索引集(Indices,index的复数):逻辑上的完整索引 collection1
  • 分片(shard):数据拆分后的各个部分
  • 副本(replica):每个分片的复制

要注意的是:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。

cat:
_cat接口说明
GET /_cat/nodes查看所有节点
GET /_cat/health查看ES健康状况
GET /_cat/master查看主节点
GET /_cat/indices查看所有索引信息

/_cat/indices?v 查看所有的索引信息

image.png

es 中会默认提供上面的几个索引,表头的含义为:

字段名含义说明
healthgreen(集群完整) yellow(单点正常、集群不完整) red(单点不正常)
status是否能使用
index索引名
uuid索引统一编号
pri主节点几个
rep从节点几个
docs.count文档数
docs.deleted文档被删了多少
store.size整体占空间大小
pri.store.size主节点占
索引,文档

PUT /索引名 即为: http请求方式为put的 IP地址:9200/索引名 {…}为请求体内容 的接口

或者

image-20221010152910919

所有命令均为以这种格式实现,下边介绍详细命令:

image-20221010154050880

image-20221010155432037

image-20221010161152971

//创建/修改索引(不存在为创建,创建为更新)
//参数可选:指定分片及副本,默认分片为3,副本为2。
PUT /索引名
{
    "settings": {
        "number_of_shards": 3,      //分片
        "number_of_replicas": 2     //副本
      }
}

//查看索引
GET /索引名

//删除索引
DELETE /索引名称

//创建文档
//PUT 	提交的id如果不存在就是新增操作,如果存在就是更新操作,id不能为空   
//POST	如果不提供id会自动生成一个id,如果id存在就更新,如果id不存在就新增
PUT/POST /索引名称/类型名/编号
{
   "name":"gfk"
}

//查询文档
GET /索引/类型/id

//更新文档
//如果更新的数据和文档中的数据是一样的,那么POST方式提交是不会有任何操作的
POST /索引/类型/id/_update
{
   "doc":{
       "name":"ggg"
   }
}

//删除文档
DELETE /索引/类型/id

//批量操作
POST /gfk/system/_bulk
{"index":{"_id":"1"}}
{"name":"dpb"}
{"index":{"_id":"2"}}
{"name":"dpb2"}

//检索方式分俩种(以索引bank为例)
//1. 通过使用REST request URL 发送检索参数(uri+检索参数)
GET bank/_search # 检索bank下的所有信息,包括 type 和 docs
GET bank/_search?query=*&sort=account_number:asc
//2. 通过使用 REST request body 来发送检索参数 (uri+请求体)
GET bank/_search
{
   "query":{
       "match_all":{}
    },
    "sort":[
       {
           "account_number":"desc"  
       }
   ]
}
Query DSL

ElasticSearch提供了一个可以执行的JSON风格的DSL(domain-specific language 领域特定语言),这个被称为Query DSL

完整的语法结构

{
   QUERY_NAME:{
      ARGUMENT:VALUE,
      ARGUMENT:VALUE,...
   }
}

如果是针对某个字段,那么它的结构为

{
    QUERY_NAME:{
        FIELD_NAME:{
            ARGUMENT:VALUE,
            ARGUMENT:VALUE,...
        }
    }
}
//match:条件匹配
//如果对应的字段是基本类型(非字符串类型),则是精确匹配
//如果对应的字段是字符串类型,则是全文检索(类似模糊查询),会ik分词
//以索引bank为例,下同
GET bank/_search  
{
   "query":{
       "match":{
          "account_number":20
      }
   }
}

//match_phrase
//将需要匹配的值当成一个整体单词(不分词)进行检索,短语匹配
GET bank/_search
{
   "query":{
       "match_phrase":{
          "address":"mill road"
      }
   }
}

//multi_match:多字段匹配
GET bank/_search
{
   "query":{
       "multi_match":{
          "query":"mill road",
          "fields":["address","state"]
      }
   }
}

//bool:复合查询
//must(与)、must_not(非)、should(或)
//must:必须满足
//must_not:必须不满足
//should:不满足也会显示,满足的话相关性分数高
GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

//filter结果过滤
//range范围  gte-lte
GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

//term:和match一样,匹配某个属性的值,全文检索字段用match,其他非text字段匹配用term
//term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串
GET bank/_search
{
   "query":{
       "term":{
          "account_number":20
      }
   }
}

//terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
GET bank/_search
{
    "query":{
        "terms":{
            "price":[2699.00,2899.00,3899.00]
        }
    }
}

//includes:来指定想要显示的字段
GET /gfk/_search
{
  "_source": {
    "includes":["title","price"]
  },
  "query": {
    "term": {
      "price": 2699
    }
  }
}

//excludes:来指定不想要显示的字段
GET /gfk/_search
{
  "_source": {
     "excludes": ["images"]
  },
  "query": {
    "term": {
      "price": 2699
    }
  }
}

//fuzzy 查询是 term 查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:
//上面的查询,也能查询到apple手机
//我们可以通过fuzziness来指定允许的编辑距离:
GET /heima/_search
{
  "query": {
    "fuzzy": {
        "title": {
            "value":"appla",
            "fuzziness":1
        }
    }
  }
}

//sort 可以让我们按照不同的字段进行排序,并且通过order指定排序的方式
GET /gfk/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

//假定我们想要结合使用 price和 _score(得分) 进行查询,并且匹配的结果首先按照价格排序,然后按照相关性得分排序:
GET /gfk/_search
{
    "query":{
        "bool":{
        	"must":{ "match": { "title": "小米手机" }},
        	"filter":{
                "range":{"price":{"gt":200000,"lt":300000}}
        	}
        }
    },
    "sort": [
      { "price": { "order": "desc" }},
      { "_score": { "order": "desc" }}
    ]
}
桶,度量

桶(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:求总数
  • ……

下面通过详细案例来体会下

//桶:
//我们按照 汽车的颜色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:这个桶中的文档数量


//为刚刚的聚合结果添加 求价格平均值的度量
GET /cars/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                }
            }
        }
    }
}
- aggs:我们在上一个aggs(popular_colors)中添加新的aggs。可见度量也是一个聚合
- avg_price:聚合的名称
- avg:度量的类型,这里是求平均值
- field:度量运算的字段

//统计每种颜色的汽车中,分别属于哪个制造商,按照make字段再进行分桶
GET /cars/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                },
                "maker":{
                    "terms":{
                        "field":"make"
                    }
                }
            }
        }
    }
}
- 原来的color桶和avg计算我们不变
- maker:在嵌套的aggs下新添一个桶,叫做maker
- terms:桶的划分类型依然是词条
- filed:这里根据make字段进行划分

//阶梯分桶
//histogram是把数值类型的字段,按照一定的阶梯大小进行分组。你需要指定一个阶梯值(interval)来划分阶梯大小
//比如你有价格字段,如果你设定interval的值为200,那么阶梯就会是这样的:
//0,200,400,600,...
//参数min_doc_count为1,来约束最少文档数量为1,这样文档数量为0的桶会被过滤
GET /cars/_search
{
  "size":0,
  "aggs":{
    "price":{
      "histogram": {
        "field": "price",
        "interval": 5000,
        "min_doc_count": 1   #不加的话会有很多文档数量为0的桶也显示
      }
    }
  }
}
映射

映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等

//创建映射字段
PUT /索引库名/_mapping/类型名称
{
  "properties": {
    "字段名": {
      "type": "类型",
      "index": true,
      "store": true,
      "analyzer": "分词器"
    }
  }
}
类型名称:就是前面将的type的概念,类似于数据库中的不同表
字段名:类似于列名,properties下可以指定许多字段。
每个字段可以有很多属性。例如:
- type:类型,可以是text、long、short、date、integer、object等
- index:是否索引,默认为true
- store:是否存储,默认为false
- analyzer:分词器,这里使用ik分词器:ik_max_word或者ik_smart

//新增映射字段
//创建完成索引的映射关系后,又要添加新的字段的映射,第一个就是先删除索引,然后调整后再新建索引映射,还有一个方式就在已有的基础上新增。
PUT /my_index/_mapping
{
  "properties":{
    "employee-id":{
      "type":"keyword"
      ,"index":false
    }
  }
}

//对于存在的映射字段,我们不能更新,更新必须创建新的索引进行数据迁移
//数据迁移
POST_reindex [固定写法]
{
   "source":{
      "index":"twitter"
    },
    "dest":{
      "index":"new_twitter"
   }
}
//老的数据有type的情况
{
   "source":{
      "index":"twitter",
      "type":"account"
    },
    "dest":{
      "index":"new_twitter"
   }
}

//查看映射
GET /索引库名/_mapping
  • 注意:创建文档就是给es里添加数据,映射就相当于创建字段,如果没有映射时添加文档,系统会自动动态的生成映射

Spring-Data-Elasticsearch

1.导入jar包
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
2.配置yml文件
spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300
3.创建对应实体类
@Document(indexName = "item",type = "docs", shards = 1, replicas = 0)
public class Item {
    @Id
    private Long id;
    
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title; //标题
    
    @Field(type = FieldType.Keyword)
    private String category;// 分类
    
    @Field(type = FieldType.Keyword)
    private String brand; // 品牌
    
    @Field(type = FieldType.Double)
    private Double price; // 价格
    
    @Field(index = false, type = FieldType.Keyword)
    private String images; // 图片地址
}
- @Document 作用在类,标记实体类为文档对象,一般有四个属性
  - indexName:对应索引库名称
  - type:对应在索引库中的类型,后期版本将会废除点type,所以在以后的版本将没有type !!!!!!!!
  - shards:分片数量,默认5
  - replicas:副本数量,默认1
- @Id 作用在成员变量,标记一个字段作为id主键
- @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
  - type:字段类型,取值是枚举:FieldType
  - index:是否索引,布尔类型,默认是true
  - store:是否存储,布尔类型,默认是false
  - analyzer:分词器名称:ik_max_word
4.创建索引和映射
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ItcastElasticsearchApplication.class)
public class IndexTest {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Test
    public void testCreate(){
        // 创建索引,会根据Item类的@Document注解信息来创建
        elasticsearchTemplate.createIndex(Item.class);
        // 配置映射,会根据Item类中的id、Field等字段来自动完成映射
        elasticsearchTemplate.putMapping(Item.class);
    }
}
5.删除索引api
@Test
public void deleteIndex() {
    elasticsearchTemplate.deleteIndex("heima");
}
6.创建接口,来进行对es的crud
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
}
7.新增文档
@Autowired
private ItemRepository itemRepository;

@Test
public void index() {
    Item item = new Item(1L, "小米手机7", " 手机",
                         "小米", 3499.00, "http://image.leyou.com/13123.jpg");
    itemRepository.save(item);
}
8.批量新增
@Test
public void indexList() {
    List<Item> list = new ArrayList<>();
    list.add(new Item(2L, "坚果手机R1", " 手机", "锤子", 3699.00, "http://image.leyou.com/123.jpg"));
    list.add(new Item(3L, "华为META10", " 手机", "华为", 4499.00, "http://image.leyou.com/3.jpg"));
    // 接收对象集合,实现批量新增
    itemRepository.saveAll(list);
}
9.修改文档

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

10.基本查询
@Test
public void testQuery(){
    Optional<Item> optional = this.itemRepository.findById(1l);
    System.out.println(optional.get());
}

@Test
public void testFind(){
    // 查询全部,并按照价格降序排序
    Iterable<Item> items = this.itemRepository.findAll(Sort.by(Sort.Direction.DESC, "price"));
    items.forEach(item-> System.out.println(item));
}
11.自定义方法

Spring Data 的另一个强大功能,是根据方法名称自动实现功能。

比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。

当然,方法名称要符合一定的约定:

KeywordSampleElasticsearch Query String
AndfindByNameAndPrice{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
OrfindByNameOrPrice{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
IsfindByName{"bool" : {"must" : {"field" : {"name" : "?"}}}}
NotfindByNameNot{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
BetweenfindByPriceBetween{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
LessThanEqualfindByPriceLessThan{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
GreaterThanEqualfindByPriceGreaterThan{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
BeforefindByPriceBefore{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
AfterfindByPriceAfter{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
LikefindByNameLike{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
StartingWithfindByNameStartingWith{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
EndingWithfindByNameEndingWith{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
Contains/ContainingfindByNameContaining{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}}
InfindByNameIn(Collection<String>names){"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
NotInfindByNameNotIn(Collection<String>names){"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
NearfindByStoreNearNot Supported Yet !
TruefindByAvailableTrue{"bool" : {"must" : {"field" : {"available" : true}}}}
FalsefindByAvailableFalse{"bool" : {"must" : {"field" : {"available" : false}}}}
OrderByfindByAvailableTrueOrderByNameDesc{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}
12.1 高级查询-基本查询
@Test
public void testQuery(){
    // 词条查询
    MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");
    // 执行查询
    Iterable<Item> items = this.itemRepository.search(queryBuilder);
    items.forEach(System.out::println);
}
12.2 高级查询-自定义查询
@Test
public void testNativeQuery(){
    // 构建查询条件
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 添加基本的分词查询
    queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米"));
    // 执行搜索,获取结果
    Page<Item> items = this.itemRepository.search(queryBuilder.build());
    // 打印总条数
    System.out.println(items.getTotalElements());
    // 打印总页数
    System.out.println(items.getTotalPages());
    items.forEach(System.out::println);
}
NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
Page<item>:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
- totalElements:总条数
- totalPages:总页数
- Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据
- 其它属性
12.3 高级查询-分页查询
@Test
public void testNativeQuery(){
    // 构建查询条件
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 添加基本的分词查询
    queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));

    // 初始化分页参数
    int page = 0;
    int size = 3;
    // 设置分页参数
    queryBuilder.withPageable(PageRequest.of(page, size));

    // 执行搜索,获取结果
    Page<Item> items = this.itemRepository.search(queryBuilder.build());
    // 打印总条数
    System.out.println(items.getTotalElements());
    // 打印总页数
    System.out.println(items.getTotalPages());
    // 每页大小
    System.out.println(items.getSize());
    // 当前页
    System.out.println(items.getNumber());
    items.forEach(System.out::println);
}
12.4 高级查询-排序
@Test
public void testSort(){
    // 构建查询条件
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 添加基本的分词查询
    queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));

    // 排序
    queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));

    // 执行搜索,获取结果
    Page<Item> items = this.itemRepository.search(queryBuilder.build());
    // 打印总条数
    System.out.println(items.getTotalElements());
    items.forEach(System.out::println);
}
13.聚合
//按照品牌brand进行分组
@Test
public void testAgg(){
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 不查询任何结果
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
    // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
    queryBuilder.addAggregation(
        AggregationBuilders.terms("brands").field("brand"));
    // 2、查询,需要把结果强转为AggregatedPage类型
    AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
    // 3、解析
    // 3.1、从结果中取出名为brands的那个聚合,
    // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
    StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
    // 3.2、获取桶
    List<StringTerms.Bucket> buckets = agg.getBuckets();
    // 3.3、遍历
    for (StringTerms.Bucket bucket : buckets) {
        // 3.4、获取桶中的key,即品牌名称
        System.out.println(bucket.getKeyAsString());
        // 3.5、获取桶中的文档数量
        System.out.println(bucket.getDocCount());
    }
}

//嵌套聚合,求平均值
@Test
public void testSubAgg(){
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 不查询任何结果
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
    // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
    queryBuilder.addAggregation(
        AggregationBuilders.terms("brands").field("brand")
        .subAggregation(AggregationBuilders.avg("priceAvg").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值
    );
    // 2、查询,需要把结果强转为AggregatedPage类型
    AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
    // 3、解析
    // 3.1、从结果中取出名为brands的那个聚合,
    // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
    StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
    // 3.2、获取桶
    List<StringTerms.Bucket> buckets = agg.getBuckets();
    // 3.3、遍历
    for (StringTerms.Bucket bucket : buckets) {
        // 3.4、获取桶中的key,即品牌名称  3.5、获取桶中的文档数量
        System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台");

        // 3.6.获取子聚合结果:
        InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg");
        System.out.println("平均售价:" + avg.getValue());
    }

}

关键API:

  • AggregationBuilders:聚合的构建工厂类。所有聚合都由这个类来构建,看看他的静态方法:

    1526567597724

  • AggregatedPage:聚合查询的结果类。它是Page<T>的子接口:

    1526567748355

    AggregatedPagePage功能的基础上,拓展了与聚合相关的功能,它其实就是对聚合结果的一种封装,大家可以对照聚合结果的JSON结构来看。

    1526567889455

    而返回的结果都是Aggregation类型对象,不过根据字段类型不同,又有不同的子类表示

    1526568128210

我们看下页面的查询的JSON结果与Java类的对照关系:

1526571200130

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值