乐优商城(06)–门户网站
一、前台系统搭建
门户系统面向的是用户,安全性很重要,而且搜索引擎对于单页应用并不友好。因此门户系统不再采用与后台系统类似的SPA(单页应用)。依然是前后端分离,不过前端的页面会使用独立的html,在每个页面中使用vue来做页面渲染。
1.1、创建工程
1.2、导入静态资源
将压缩包leyou-portal
解压,并将其复制到新建的项目中
1.3、安装live-server
没有webpack,就无法使用webpack-dev-server运行这个项目,实现热部署。
所以,这里要使用另外一种热部署方式:live-server
地址:https://www.npmjs.com/package/live-server
这是一款带有热加载功能的小型开发服务器。用它来展示你的HTML / JavaScript / CSS,但不能用于部署最终的网站。
安装和运行参数
安装,使用npm命令即可,这里建议全局安装,以后任意位置可用
npm install -g live-server
运行时,直接输入命令:
live-server
另外,你可以在运行命令后,跟上一些参数以配置:
--port=NUMBER
- 选择要使用的端口,默认值:PORT env var或8080--host=ADDRESS
- 选择要绑定的主机地址,默认值:IP env var或0.0.0.0(“任意地址”)--no-browser
- 禁止自动Web浏览器启动--browser=BROWSER
- 指定使用浏览器而不是系统默认值--quiet | -q
- 禁止记录--verbose | -V
- 更多日志记录(记录所有请求,显示所有侦听的IPv4接口等)--open=PATH
- 启动浏览器到PATH而不是服务器root--watch=PATH
- 用逗号分隔的路径来专门监视变化(默认值:观看所有内容)--ignore=PATH
- 要忽略的逗号分隔的路径字符串(anymatch -compatible definition)--ignorePattern=RGXP
-文件的正则表达式忽略(即.*\.jade
)(不推荐使用赞成--ignore
)--middleware=PATH
- 导出要添加的中间件功能的.js文件的路径; 可以是没有路径的名称,也可以是引用middleware
文件夹中捆绑的中间件的扩展名--entry-file=PATH
- 提供此文件(服务器根目录)代替丢失的文件(对单页应用程序有用)--mount=ROUTE:PATH
- 在定义的路线下提供路径内容(可能有多个定义)--spa
- 将请求从/ abc转换为/#/ abc(方便单页应用)--wait=MILLISECONDS
- (默认100ms)等待所有更改,然后重新加载--htpasswd=PATH
- 启用期待位于PATH的htpasswd文件的http-auth--cors
- 为任何来源启用CORS(反映请求源,支持凭证的请求)--https=PATH
- 到HTTPS配置模块的路径--proxy=ROUTE:URL
- 代理ROUTE到URL的所有请求--help | -h
- 显示简洁的使用提示并退出--version | -v
- 显示版本并退出
测试
打开idea页面上的控制台,输入命令live-server
,默认端口为8080
1.4、域名访问
修改hosts文件
127.0.0.1 www.leyou.com
修改本地nginx配置,将www.leyou.com反向代理到127.0.0.1:8080
server {listen 80;server_name www.leyou.com;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Server $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;location / {proxy_pass http://127.0.0.1:8080;proxy_connect_timeout 600;proxy_read_timeout 600;}
}
重新加载nginx配置:nginx.exe -s reload
1.5、common.js简单介绍
为了方便后续的开发,在前台系统中定义了一些工具,放在了common.js中:
首先对axios进行了一些全局配置,请求超时时间,请求的基础路径,是否允许跨域操作cookie等
定义了对象 ly ,也叫leyou,包含了下面的属性:
- getUrlParam(key):获取url路径中的参数
- http:axios对象的别名。以后发起ajax请求,可以用ly.http.get()
- store:localstorage便捷操作,后面用到再详细说明
- formatPrice:格式化价格,如果传入的是字符串,则扩大100被并转为数字,如果传入是数字,则缩小100倍并转为字符串
- formatDate(val, pattern):对日期对象val按照指定的pattern模板进行格式化
- stringify:将对象转为参数字符串
- parse:将参数字符串变为js对象
ElasticSearch学习笔记
一、ElasticSearch概述
Elasticsearch官网:https://www.elastic.co/cn/products/elasticsearch
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
Elaticsearch,简称为es ,es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据﹔本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
如上所述,Elasticsearch具备以下特点:
- 分布式,无需人工搭建集群(solr就需要人为配置,使用Zookeeper作为注册中心)
- Restful风格,一切API都遵循Rest原则,容易上手
- 近实时搜索,数据更新在Elasticsearch中几乎是完全同步的。
Lucene和ElasticSearch的关系
- Lucene是一套信息检索工具包! jar包!不包含搜索引擎系统!包含的:索引结构!读写索引的工具!排序,搜索规则…工具类!
- ElasticSearch是基于Lucene做了一些封装和增强
二、ElasticSearch的安装
环境准备:Centos7、JDK1.8及以上,ES的版本要和springBoot中的版本对应。
2.1、安装和配置
官网下载Linux的7.6.2版本的压缩包
2.1.1、新建一个用户leyou
出于安全考虑,elasticsearch默认不允许以root账号运行。
创建用户:
useradd leyou
切换用户:
su - leyou
2.1.2、上传安装包,并解压
将安装包上传到:/home/leyou目录,一定要先创建leyou用户,创建后会自动生成该目录
#解压缩
tar -zxvf elasticsearch-7.6.2-linux-x86_64.tar.gz
把目录重命名:
mv elasticsearch-7.6.2/ elasticsearch
进入,查看目录结构:
bin 启动文件
config 配置文件- log4j2.properties 日志配置文件- jvm.options Java虚拟机相关配置- elasticsearch.yml ES配置文件 默认9200端口
lib 相关jar包
logs 日志
modules 功能模块
plugins 插件
2.1.3、修改配置
进入config目录:cd config
需要修改的配置文件有两个:
- jvm.options
Elasticsearch基于Lucene的,而Lucene底层是java实现,因此需要配置jvm参数。
编辑jvm.options:
vim jvm.options
默认配置如下:
-Xms1g
-Xmx1g
内存占用太多了,我们调小一些:
-Xms512m
-Xmx512m
- elasticsearch.yml
vim elasticsearch.yml
修改数据和日志目录:
path.data: /home/leyou/elasticsearch/data # 数据目录位置
path.logs: /home/leyou/elasticsearch/logs # 日志目录位置
把data和logs目录修改指向了elasticsearch的安装目录。但是这data目录并不存在,因此需要创建出来。
进入elasticsearch的根目录,然后创建:
mkdir data
修改绑定的ip
network.host: 0.0.0.0 # 绑定到0.0.0.0,允许任何ip来访问
默认只允许本机访问,修改为0.0.0.0后则可以远程访问
目前是做的单机安装,如果要做集群,只需要在这个配置文件中添加其它节点信息即可。
elasticsearch.yml的其它可配置信息:
属性名 | 说明 |
---|---|
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 |
2.2、运行
进入elasticsearch/bin目录,可以看到下面的执行文件:
./elasticsearch
运行后,发现报错:
错误1、进程虚拟内存
max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
首先切换回root用户
vm.max_map_count:限制一个进程可以拥有的VMA(虚拟内存区域)的数量,继续修改配置文件, :
vim /etc/sysctl.conf
添加下面内容:
vm.max_map_count=655360
然后执行命令:
sysctl -p
错误2、节点错误
the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
在elasticsearch
的config
目录下,修改elasticsearch.yml
配置文件,将下面的配置加入到该配置文件中:
ip替换host1等,多节点请添加多个ip地址,单节点可写按默认来node.name: node-1
cluster.initial_master_nodes: ["node-1"] #这里的node-1为node-name配置的值
重启终端窗口,再次切换回leyou用户,启动
浏览器访问:ip地址+9200
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ti7v3LLG-1630373777912)(F:\MyProject\乐优商城笔记\assets\06-es浏览器访问.png)]
注意:阿里云安全组需要开放两个端口:
- 9300:集群节点间通讯接口
- 9200:客户端访问接口
三、安装kibana
3.1、什么是Kibana?
Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana ,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板( dashboard )) 实时显示Elasticsearch查询动态。设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。
3.2、安装
这里选则安装在本地Windos系统中,方便操作查看,官网下载安装包,版本要与elasticsearch保持一致,即7.6.2
3.3、配置运行
Kibana可以汉化,将Kibana的配置文件中kibana.yml
的i18n.locale: "en"
改为zh-CN即可
配置
进入安装目录下的config目录,修改kibana.yml文件:
修改elasticsearch服务器的地址:
elasticsearch.url: "http://192.168.56.101:9200" #es的安装机器ip地址
运行
进入安装目录下的bin目录:
双击kibana.bat
即运行,等待一段时间,运行端口为5601,访问:http://127.0.0.1:5601
选择左侧的DevTools菜单,即可进入控制台页面:
四、安装ik分词器
K分词器概念
分词 :即把一段中文或者别的词汇划分成一个个的关键字,在搜索时候会把信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如“我爱学习”会被分成“我”、“爱”、“学”,“习”,这显然是不符合要求的,所以需要安装中文分词器ik来解决这个问题。
如果要使用中文,建议使用ik分词器!
IK提供了两种分词算法: ik_smart和ik_max_word,其中 ik_smart为最少切分,ik_max_word为最细粒度划分!
安装ik分词器
-
github找到下载地址:https://github.com/medcl/elasticsearch-analysis-ik
-
下载完毕将该文件解压至elasticsearch中的plugins文件夹中即可
- 注意:解压后记得将压缩包删除
-
启动es发现插件已被加载
-
启动kibana测试
- 模式一:ik_smart(为最少切分)
- 模式二:ik_max_word(为最细粒度划分)
当发现有的词汇不会分开,可以使用自定义字典分词:
ik的config目录下存在IKAnalyzer.cfg.xml配置文件
可以进行相关配置
扩展字典可以创建文件mydir.dir文件
五、ES核心概念
ES是如何去存储数据,数据结构是什么,又是如何实现搜索的呢?
集群,节点,索引,类型,文档,分片,映射
5.1、DBMS与ES的对比
DBMS | ES |
---|---|
数据库(database) | 索引(indices) |
表(tables) | types(慢慢被弃用) |
行(rows) | documents |
字段(columns) | fields |
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档(行),每个文档中又包含多个字段(列)。
物理设计:
elasticsearch在后台把每个索引划分成多个分片,每个分片可以在集群中的不同服务器间迁移
默认的集群名称就是elaticsearh
逻辑设计:
一个索引类型中,包含多个文档,比如说文档1,文档2。当我们索引一篇文档时,可以通过这样的一各顺序找到它:索引->类型->文档ID,通过这个组合我们就能索引到某个具体的文档。
注意:ID不必是整数,实际上它是个字符串。
5.2、文档
之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:
- 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value !
- 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的!{就是一个json对象! 使用fastijson可以进行自动转换!}
- 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。
5.3、类型
类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器。类型中对于字段的定义称为映射,比如name映射为字符串类型。我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,elasticsearch会自动推断该字段是什么类型,但不一定准确,最好还是提前设定好字段的类型
5.4、索引
索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。
概念 | 说明 |
---|---|
索引库(indices) | indices是index的复数,代表许多的索引, |
类型(type) | 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念 |
文档(document) | 存入索引库原始的数据。比如每一条商品信息,就是一个文档 |
字段(field) | 文档中的属性 |
映射配置(mappings) | 字段的数据类型、属性、是否索引、是否存储等特性 |
分片的工作流程
物理设计:
一个集群至少有一个节点,而一个节点就是一个elasricsearch进程,节点可以有多个索引(默认的),若创建了一个索引,那么索引将会有个5个分片( primary shard ,又称主分片)构成的,每一个主分片会有一个副本(replica shard ,又称复制分片)
上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能知晓哪些文档包含特定的关键字。
倒排索引
倒排索引(Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
倒排索引有两种不同的反向索引形式:
- 一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。
- 一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。
5.5、字段属性详解
Elasticsearch中支持的数据类型非常丰富:
5.5.1、常用属性说明
-
String类型,又分两种:
- text:可分词,不可参与聚合
- keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
-
Numerical:数值类型,分两类
- 基本数据类型:long、interger、short、byte、double、float、half_float
- 浮点数的高精度类型:scaled_float
- 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
-
Date:日期类型
elasticsearch可以对日期格式化为字符串存储,但是建议存储为毫秒值,存储为long,节省空间。
5.5.2、index
index影响字段的索引情况。
- true:字段会被索引,则可以用来进行搜索。默认值就是true
- false:字段不会被索引,不能用来搜索
index的默认值就是true,也就是说你不进行任何配置,所有字段都会被索引。
但是有些字段是我们不希望被索引的,比如商品的图片信息,就需要手动设置index为false。
5.5.3、store
否将数据进行额外存储。
在学习lucene和solr时,我们知道如果一个字段的store设置为false,那么在文档列表中就不会有这个字段的值,用户的搜索结果中不会显示出来。
但是在Elasticsearch中,即便store设置为false,也可以搜索到结果。
原因是Elasticsearch在创建文档索引时,会将文档中的原始数据备份,保存到一个叫做_source
的属性中。而且可以通过过滤_source
来选择哪些要显示,哪些不显示。
而如果设置store为true,就会在_source
以外额外存储一份数据,多余,因此一般都会将store设置为false,事实上,store的默认值就是false。
六、API语法学习
下面的操作 都是是kibana 中的 dev Tools工具操作的
6.1、索引
Elasticsearch采用Rest风格API,因此其API就是一次http请求,可以用任何工具发起http请求
//查看所有索引相关信息
GET /_cat/indices?v
//创建索引
PUT customer
PUT /customer //库名
{"settings": {"number_of_shards": 1, //分片数量"number_of_replicas": 0 //副本数量}
}
//查看所有索引相关信息
GET customer
//删除索引
DELETE customer
添加映射
PUT /索引库名/_mapping //(自7.0起取消了Type,不需要加索引类型名,统一为_doc)
{"properties": {"字段名": {"type": "类型", //类型:可以是text、long、short、date、integer、object等"index": true, //是否索引,默认为true"store": true, //是否存储,默认为false"analyzer": "分词器" //分词器,这里的`ik_max_word`即使用ik分词器}}
}
PUT /goods/_mapping
{"properties":{"title":{"type": "text","analyzer": "ik_max_word"},"images":{"type": "keyword","index": "false"},"price":{"type":"float"}}
}
6.2、文档CRUD
6.2.1、创建文档
// create 创建文档
// _create 指定类型为create并不是type名称 默认是_doc 指定 ID 如果已经存在,就报错
PUT users/_create/1
{"user" : "Jack2","post_date" : "2019-05-15T14:12:12","message" : "trying out Elasticsearch"
}//index 创建文档
//与create的区别:index如果文档不存在,就索引新的文档。如果文档存在就覆盖原有的文档内容。版本信息+1
PUT users/_doc/1
{"tags":["guitor","skateboard","reading"]
}
6.2.2、查询文档
//get 根据文档id查询 文档内容
GET users/_doc/1
- 找到文档,返回HTTP 200。
- 文档元信息
- _index/ _type /
- 版本信息,同一个ld的文档,即使被删除,Version号也会不断增加
- _souece 中默认包含了文档的所有原始信息
- 文档元信息
- 找不到文档,返回HTTP 404
6.2.3、修改文档
//PUT 方式
//缺点:相当与重新创建该文档达到更新的效果,漏写的字段会丢失
PUT users/_doc/1
{"tags":["guitor","skateboard","reading","testing"]
}//POST update 方式
//update 修改文档 不会删除原文档 而是在 文档的基础上更新文档中的字段内容
//该_update字段才会根据文档中字段信息 在原文档上增加字段 必须带有docPOST users/_update/1/{"doc":{"post_date" : "2019-05-18T14:12:12","message" : "trying out ElasticsearchOut","phone" : "1806185","pubtest":[1,2,3],"pubtest2":"[1,2,3]"}}
6.2.4、删除文档
DELETE users/_doc/1
6.2.5、索引的 maping
maping 相当于表的 schema
//查看mapping信息
GET users/_mapping//创建mapping
PUT /goods
{
"mappings": {"properties": {"title": {"type": "text","analyzer": "ik_max_word"},"images": {"type": "keyword"},"price": {"type": "float"}}
}
}
6.3、文档批量操作
6.3.1、bulk api批量操作
- bulk相当于数据库里的bash操作
- 引入批量操作bulk,提高工作效率
- bulk API可以同时执行多个请求
bulk的格式
action:index/create/update/deletemetadata:_index,_type,_idrequest body:_source (删除操作不需要加request body){ action: { metadata }}{ request body }//单条操作失败不会影响其他操作
-
bulk不支持get操作,因为批量操作,里面放get操作,没啥用!所以,官方也不支持
-
bulk会把将要处理的数据载入内存中,所以数据量是有限制的,最佳的数据量不是一个确定的数值,它取决于机器硬件、文档大小以及复杂性、索引以及搜索的负载。
一般建议是1000-5000个文档,如果文档很大,可以适当减少队列,大小建议是5-15MB,默认不能超过100M,可以在es的配置文件(即$ES_HOME下的config下的elasticsearch.yml)中配置
//Bulk 操作 批量操作,其中一步错误,不影响其他操作PUT _bulk
{"index":{"_index":"test","_id":"1"}}
{"name":"dukun","post_date" : "2019-05-18T14:12:12","age":18,"phone" : "1806185","sex":"男"}
{"index":{"_index":"test","_id":"2"}}
{"name":"dukun02","post_date" : "2019-05-18T14:12:12","age":25,"phone" : "19888","sex":"男"}
{"create":{"_index":"test","_id":"3"}}
{"name":"dukun03","post_date" : "2019-05-19T14:12:12"}
{"update":{"_index":"test","_id":"3"}}
{"doc" :{"name":"dukun04","phone" : "1806185","age":28,"sex":"男"}}
{"delete":{"_index":"test","_id":"4"}}
6.3.2、mget批量读取
批量操作可以减少网络连接所带来的开销,提升性能
//批量查询mget
GET /_mget
{"docs":[{"_index" : "test","_id" : "1"},{"_index" : "test","_id" : "3"}]
}
//批量查询mget url中指定索引 可以简化如下
GET test/_mget
{"ids":[1,2,3]
}
//批量查询mget 中_source过滤,默认_source字段会返回所有的内容
//比如使用_source,_source_include 包含字段,_source_exclude 查询排除字段."_source" : false 不显示字段
GET test/_mget
{"docs":[{"_id":"1","_source" : false},{"_id":"1","_source" : true},{"_id":"2","_source" : ["name","age"]},{"_id":"3","_source" : {"include":["name","age","sex"]}},{"_id":"3","_source" : {"exclude":["name","age","sex"]}}]
}
6.4、查询
6.4.1、基本查询
基本语法
GET /索引库名/_search
{"query":{"查询类型":{"查询条件":"查询条件值"}}
}
这里的query代表一个查询对象,里面可以有不同的查询属性
- 查询类型:
- 例如:
match_all
,match
,term
,range
等等
- 例如:
- 查询条件:查询条件会根据类型的不同,写法也有差异
新增数据
POST /goods/_doc/1
{"title":"小米手机","images":"http://image.leyou.com/12479122.jpg","price":2699.00
}POST /goods/_doc/2
{"title":"大米手机","images":"http://image.leyou.com/12479122.jpg","price":2899.00
}
6.4.1.1、查询所有(match_all)
示例
GET /goods/_search
{"query":{"match_all": {}}
}
query
:代表查询对象match_all
:代表查询所有
6.4.1.2、匹配查询(match)
新增数据
PUT /goods/_doc/3
{"title":"小米电视4A","images":"http://image.leyou.com/12479122.jpg","price":3899.00
}
or关系
match
类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是or的关系
GET /goods/_search
{"query":{"match":{"title":"小米电视"}}
}
不仅会查询到电视,而且与小米相关的都会查询到,多个词之间是or
的关系
and关系
某些情况下,需要更精确查找,希望这个关系变成and
,可以这样做:
GET /goods/_search
{"query":{"match": {"title": {"query": "小米电视","operator": "and"}}}
}
只有同时包含小米
和电视
的词条才会被搜索到。
or和and之间?
match
查询支持 minimum_should_match
最小匹配参数, 这可以指定必须匹配的词项数用来表示一个文档是否相关。可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数
GET /goods/_search
{"query":{"match":{"title":{"query":"小米曲面电视","minimum_should_match": "75%"}}}
}
"小米曲面电视"搜索语句可以分为3个词,如果使用and关系,需要同时满足3个词才会被搜索到。这里采用最小品牌数:75%,那么也就是说只要匹配到总词条数量的75%即可,这里3*75% 约等于2。所以只要包含2个词条就算满足条件了。
6.4.1.3、 多字段查询(multi_match)
multi_match
与match
类似,不同的是它可以在多个字段中查询
GET /goods/_search
{"query":{"multi_match": {"query": "小米","fields": [ "title", "subTitle" ]}}
}
6.4.1.4、词条匹配(term)
term
查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串
GET /goods/_search
{"query":{"term":{"price":2699.00}}
}
6.4.1.5、多词条精确匹配(terms)
terms
查询和 term 查询一样,但它允许指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
GET /goods/_search
{"query":{"terms":{"price":[2699.00,2899.00,3899.00]}}
}
6.4.2、结果过滤
默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source
的所有字段都返回。
如果只想获取其中的部分字段,可以添加_source
的过滤
6.4.2.1、直接指定字段
GET /goods/_search
{"_source": ["title","price"],"query": {"term": {"price": 2699}}
}
6.4.2.2、指定includes和excludes
- includes:来指定想要显示的字段
- excludes:来指定不想要显示的字段
二者都是可选的。
GET /goods/_search
{"_source": {"includes":["title","price"]},"query": {"term": {"price": 2699}}
}
与下面的结果将是一样的:
GET /goods/_search
{"_source": {"excludes": ["images"]},"query": {"term": {"price": 2699}}
}
6.4.3、高级查询
6.4.3.1、布尔组合(bool)
bool
把各种其它查询通过must
(与)、must_not
(非)、should
(或)的方式进行组合
GET /goods/_search
{"query":{"bool":{"must": { "match": { "title": "大米" }},"must_not": { "match": { "title": "电视" }},"should": { "match": { "title": "手机" }}}}
}
6.4.3.2、范围查询(range)
range
查询找出那些落在指定区间内的数字或者时间
GET /goods/_search
{"query":{"range": {"price": {"gte": 1000.0,"lt": 2800.00}}}
}
range
查询允许以下字符:
操作符 | 说明 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
6.4.3.3、模糊查询(fuzzy)
新增数据
POST /goods/_doc/4
{"title":"apple手机","images":"http://image.leyou.com/12479122.jpg","price":6899.00
}
fuzzy
查询是 term
查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:
GET /goods/_search
{"query": {"fuzzy": {"title": "appla"}}
}
可以通过fuzziness
来指定允许的编辑距离:
GET /goods/_search
{"query": {"fuzzy": {"title": {"value":"appla","fuzziness":1}}}
}
6.4.4、 过滤(filter)
条件查询中进行过滤
所有的查询都会影响到文档的评分及排名。如果需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter
方式:
GET /goods/_search
{"query":{"bool":{"must":{ "match": { "title": "小米手机" }},"filter":{"range":{"price":{"gt":2000.00,"lt":3800.00}}}}}
}
注意:filter
中还可以再次进行bool
组合条件过滤。
无查询条件,直接过滤
如果一次查询只有过滤,没有查询条件,不希望进行评分,可以使用constant_score
取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。
GET /goods/_search
{"query": {"constant_score": {"filter": {"range": {"price": {"gt": 2000.00,"lt": 3000.00}}}}}
}
6.4.5、排序
6.4.5.1、 单字段排序
sort
可以让按照不同的字段进行排序,并且通过order
指定排序的方式
GET /goods/_search
{"query": {"match": {"title": "小米手机"}},"sort": [{"price": {"order": "desc"}}]
}
6.4.5.2、 多字段排序
假定想要结合使用 price和 _score(得分) 进行查询,并且匹配的结果首先按照价格排序,然后按照相关性得分排序:
GET /goods/_search
{"query":{"bool":{"must":{ "match": { "title": "小米手机" }}}},"sort": [{ "price": { "order": "desc" }},{ "_score": { "order": "desc" }}]
}
6.5、聚合aggregations
聚合可以极其方便的实现对数据的统计、分析。例如:
- 什么品牌的手机最受欢迎?
- 这些手机的平均价格、最高价格、最低价格?
- 这些手机每月的销售情况如何?
实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。
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": {"properties": {"color": {"type": "keyword"},"make": {"type": "keyword"}}}
}
注意:在ES中,需要进行聚合、排序、过滤的字段其处理方式比较特殊,因此不能被分词。这里将color和make这两个文字类型的字段设置为keyword类型,这个类型不会被分词,将来就可以参与聚合
导入数据
POST /cars/_doc/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2020-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2020-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2020-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2020-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2020-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2020-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2020-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2020-02-12" }
6.5.1、聚合为桶
首先,按照 汽车的颜色color
来划分桶
GET /cars/_search
{"size" : 0,"aggs" : { "popular_colors" : { "terms" : { "field" : "color"}}}
}
- size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
- aggs:声明这是一个聚合查询,是aggregations的缩写
- popular_colors:给这次聚合起一个名字,任意。
- terms:划分桶的方式,这里是根据词条划分
- field:划分桶的字段
- terms:划分桶的方式,这里是根据词条划分
- popular_colors:给这次聚合起一个名字,任意。
- hits:查询结果为空,因为我们设置了size为0
- aggregations:聚合的结果
- popular_colors:我们定义的聚合名称
- buckets:查找到的桶,每个不同的color字段值都会形成一个桶
- key:这个桶对应的color字段的值
- doc_count:这个桶中的文档数量
通过聚合的结果可以发现,目前红色的小车比较畅销!
6.5.2、桶内度量
前面的例子查询出每个桶里面的文档数量,这很有用。 但通常,我们的应用需要提供更复杂的文档度量。 例如,每种颜色汽车的平均价格是多少?
因此,需要告诉Elasticsearch使用哪个字段
,使用何种度量方式
进行运算,这些信息要嵌套在桶
内,度量
的运算会基于桶
内的文档进行
现在,为刚刚的聚合结果添加 求价格平均值的度量:
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:度量运算的字段
可以看到每个桶中都有自己的avg_price
字段,这是度量聚合的结果
6.5.2、桶内嵌套桶
统计每种颜色的汽车中,分别属于哪个制造商,按照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字段进行划分
- 可以看到,新的聚合
maker
被嵌套在原来每一个color
的桶中。 - 每个颜色下面都根据
make
字段进行了分组 - 能读取到的信息:
- 红色车共有4辆
- 红色车的平均售价是 $32,500 美元。
- 其中3辆是 Honda 本田制造,1辆是 BMW 宝马制造。
6.5.3、划分桶的其它方式
阶梯分桶Histogram
原理:
histogram是把数值类型的字段,按照一定的阶梯大小进行分组。需要指定一个阶梯值(interval)来划分阶梯大小。
举例:
比如有价格字段,如果设定interval的值为200,那么阶梯就会是这样的:0,200,400,600,…
上面列出的是每个阶梯的key,也是区间的起点。
如果一件商品的价格是450,会落入哪个阶梯区间呢?计算公式如下:
bucket_key = Math.floor((value - offset) / interval) * interval + offset
value:就是当前数据的值,本例中是450
offset:起始偏移量,默认为0
interval:阶梯间隔,比如200
因此得到的key = Math.floor((450 - 0) / 200) * 200 + 0 = 400
GET /cars/_search
{"size":0,"aggs":{"price":{"histogram": {"field": "price","interval": 5000}}}
}
可以发现,结果中有大量的文档数量为0 的桶,不够美观
可以增加一个参数min_doc_count为1,来约束最少文档数量为1,这样文档数量为0的桶会被过滤
GET /cars/_search
{"size":0,"aggs":{"price":{"histogram": {"field": "price","interval": 5000,"min_doc_count": 1}}}
}
范围分桶range
范围分桶与阶梯分桶类似,也是把数字按照阶段进行分组,只不过range方式需要你自己指定每一组的起始和结束大小。
GET /cars/_search
{"size":0,"aggs":{"price":{"range": {"field": "price","ranges": [{"from": 10000.0,"to": 15000.0},{"from": 15000.0,"to": 20000.0}]}}}
}
七、Spring Data Elasticsearch
7.1、简介
Spring Data Elasticsearch是Spring Data项目下的一个子模块。
查看 Spring Data的官网:http://projects.spring.io/spring-data/
Spring Data的使命是为数据访问提供熟悉且一致的基于Spring的编程模型,同时仍保留底层数据存储的特殊特性。它使得使用数据访问技术,关系数据库和非关系数据库,map-reduce框架和基于云的数据服务变得容易。
这是一个总括项目,其中包含许多特定于给定数据库的子项目。这些令人兴奋的技术项目背后,是由许多公司和开发人员合作开发的。
Spring Data Elasticsearch的页面:https://projects.spring.io/spring-data-elasticsearch/
特征:
- 支持Spring的基于
@Configuration
的java配置方式,或者XML配置方式 - 提供了用于操作ES的便捷工具类**
ElasticsearchTemplate
**。包括实现文档到POJO之间的自动智能映射。 - 利用Spring的数据转换服务实现的功能丰富的对象映射
- 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式
- 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询
7.2、创建Demo工程
7.2.1、相关配置:
<?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.leyou.demo</groupId><artifactId>elasticsearch</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>elasticsearch</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.11.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
application.yaml文件配置:
server:port: 18000
spring:application:name: elasticsearchelasticsearch:rest:uris: http://114.55.32.156:9200
7.2.2、实体类及配置类
@Document(indexName = "item",type = "docs", shards = 1, replicas = 0)
public class Item {@Idprivate 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:映射类型。如果未设置,则使用小写的类的简单名称。(从版本4.0开始不推荐使用)
- shards:索引的分片数
- replicas:索引的副本数
- refreshIntervall :索引的刷新间隔。用于索引创建。默认值为“ 1s”。
- indexStoreType: 索引的索引存储类型。用于索引创建。默认值为“ fs”。
- createIndex: 配置是否在存储库引导中创建索引。默认值为true。
- versionType: 版本管理的配置。默认值为EXTERNAL。
- @Id 作用在成员变量,标记一个字段作为id主键
- @Transient :默认情况下,存储或检索文档时,所有字段都映射到文档,此注释不包括该字段。
- @PersistenceConstructor: 标记从数据库实例化对象时要使用的给定构造函数,甚至是受保护的程序包。构造函数参数按名称映射到检索到的Document中的键值。
- @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
- type:字段类型,取值是枚举:FieldType
- index:是否索引,布尔类型,默认是true
- store:是否存储,布尔类型,默认是false
- analyzer:插入是使用分词器名称
- searchAnalyzer :查询是使用分词器
7.2.3、创建索引和映射
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchstudyApplication.class)
public class ESTest {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic void createIndexTest(){//设置索引信息(绑定实体类) 返回IndexOperationsIndexOperations indexOperations = this.elasticsearchRestTemplate.indexOps(Item.class);//创建索引库,必须得注释掉,原因在于实体类中有注解指明了要创建的索引,若这里再创建会报错//indexOperations.create();//Creates the index mapping for the entity this IndexOperations is bound to.//为该IndexOperations绑定到的实体创建索引映射。 有一个为给定类创建索引的重载,需要类的字节码文件Document mapping = indexOperations.createMapping();//writes a mapping to the index 将刚刚通过类创建的映射写入索引indexOperations.putMapping(mapping);}
}
7.2.4、删除索引
@Test
public void deleteIndexTest(){//第一种删除方式//this.elasticsearchRestTemplate.indexOps(Item.class).delete();//第二种删除方式this.elasticsearchRestTemplate.indexOps(IndexCoordinates.of("item")).delete();
}
7.2.5、Repository文档操作
自定义接口继承ElasticsearchRepository<类名,id类型>
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
}
新增文档
@Autowired
private ItemRepository itemRepository;@Test
public void index() {Item item = new Item(1L, "小米手机8", " 手机","小米", 3499.00, "http://image.leyou.com/13123.jpg");itemRepository.save(item);
}
去页面查询看看:
GET /item/_search
批量新增
@Test
public void indexList() {List<Item> list = new ArrayList<>();list.add(new Item(2L, "坚果手机R4", " 手机", "锤子", 3699.00, "http://image.leyou.com/123.jpg"));list.add(new Item(3L, "华为META20", " 手机", "华为", 4499.00, "http://image.leyou.com/3.jpg"));// 接收对象集合,实现批量新增itemRepository.saveAll(list);
}
再次查询:
修改文档
修改和新增是同一个接口,区分的依据就是id,这一点跟在页面发起PUT请求是类似的。
基本查询
@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(System.out::println);
}
结果:
自定义方法
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:有个方法名叫做:findByTitle,那么它就知道是根据title查询,然后自动完成,无需写实现类。
当然,方法名称要符合一定的约定:
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or | findByNameOrPrice | {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is | findByName | {"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not | findByNameNot | {"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between | findByPriceBetween | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual | findByPriceLessThan | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual | findByPriceGreaterThan | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before | findByPriceBefore | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After | findByPriceAfter | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like | findByNameLike | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith | findByNameStartingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith | findByNameEndingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing | findByNameContaining | {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In | findByNameIn(Collection<String>names) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn(Collection<String>names) | {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | {"bool" : {"must" : {"field" : {"available" : true}}}} |
False | findByAvailableFalse | {"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy | findByAvailableTrueOrderByNameDesc | {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
例如,来按照价格区间查询,定义这样的一个方法:
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {/*** 根据价格区间查询* @param p1* @param p2* @return*/List<Item> findByPriceBetween(double p1,double p2);
}
增加数据
@Test
public void indexLists() {List<Item> list = new ArrayList<>();list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.leyou.com/13123.jpg"));// 接收对象集合,实现批量新增this.itemRepository.saveAll(list);
}
编写测试类,直接运行
@Test
public void queryByPriceBetween(){List<Item> items = this.itemRepository.findByPriceBetween(2000.00, 4000.00);items.forEach(System.out::println);
}
7.2.6、高级查询
基本查询
@Test
public void queryTest(){// 词条查询MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");// 执行查询Iterable<Item> items = this.itemRepository.search(queryBuilder);items.forEach(System.out::println);
}
Repository的search方法需要QueryBuilder参数,elasticSearch提供了一个对象QueryBuilders:
该对象提供了大量的静态方法,用于生成各种不同类型的查询对象,例如:词条、模糊、通配符等QueryBuilder对象。
自定义查询
最基本的match query:
@Test
public void testSearch1() {//利用构造器建造NativeSearchQuery 可以添加条件,过滤,等复杂操作NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchQuery("title", "小米")).build();//elasticsearchRestTemplate.search方法参数一,本机查询的构造,参数二index的类,可选参数三再次声明库名(可以多个)SearchHits<Item> search = this.elasticsearchRestTemplate.search(query, Item.class);search.forEach(searchHit-> System.out.println(searchHit.getContent()));
}
分页排序查询
SortBuilders.fieldSort("排序字段").order(SortOrder.ASC/DESC) :
分页后的排序
PageRequest.of(当前页,每页条数):
注意起始页为0
@Test
public void testNativeSearchQueryBuilder2() {NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchQuery("category", "手机"))//添加分页 注意页码是从0开始的//pageable的实现类PageRequest的静态方法of//要排序就增加参数3 Sort.Direction.ASC升 Sort.Direction.DESC降.withPageable(PageRequest.of(0,3))//排序整体//根据字段排序fieldSort("字段名") .order(SortOrder.ASC/DESC).withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)).build();//参数一,本机查询的构造,参数二index的类,可选参数三再次声明库名(可以多个)SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);search.forEach(searchHit-> System.out.println(searchHit.getContent()));
}
7.2.7、聚合查询
下面聚合查各品牌的产品数量:
AggregationBuilders
来建立聚合对象,可选多种聚合方式
例如字段聚合
:terms
中放聚合名,field
中放聚合字段
getAggregations()
从searchHits中获取聚合结果,聚合结果为Map,键为聚合名,值为聚合结果
@Test
public void testAgg1(){NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();//聚合可以有多个,所以add//terms词条聚合,传入聚合名称 field("聚合字段") size(结果集大小)NativeSearchQuery query = nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"))//结果集过滤 这里设置不需要结果集(不添加包含与不包含,会自动生成length为0数组).withSourceFilter(new FetchSourceFilterBuilder().build()).build();SearchHits<Item> hits = elasticsearchRestTemplate.search(query, Item.class);System.out.println(hits);//获取聚合结果集 因为结果为字符串类型 所以用ParsedStringTerms接收 还有ParsedLongTerms接收数字 ParsedDoubleTerms接收小数Aggregations aggregations = hits.getAggregations();assert aggregations != null;ParsedStringTerms brands = aggregations.get("brands");//获取桶brands.getBuckets().forEach(bucket->{//获取桶中的key 与 记录数System.out.println(bucket.getKeyAsString()+" "+bucket.getDocCount());});
}
嵌套聚合查询
下面查出各品牌的产品数量后再查出各品牌产品均价:
添加子聚合 subAggregation
(添加方式和上面是一样的)
获取自聚合 在父聚合的桶中用getAggregations()
获取子聚合
@Test
public void testAgg2(){NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();//聚合可以有多个,所以add//terms词条聚合,传入聚合名称 field("聚合字段")NativeSearchQuery query = nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand")//添加子聚合 subAggregation(添加方式是一样的) 值为桶中品牌均价.subAggregation(AggregationBuilders.avg("price_avg").field("price")))//结果集过滤 这里设置不需要结果集(不添加包含与不包含,会自动生成长为0数组).withSourceFilter(new FetchSourceFilterBuilder().build()).build();SearchHits<Item> hits = elasticsearchRestTemplate.search(query, Item.class);System.out.println(hits);//获取聚合结果集 因为结果为字符串类型 所以用ParsedStringTerms接收 还有ParsedLongTerms接收数字 ParsedDoubleTerms接收小数Aggregations aggregations = hits.getAggregations();assert aggregations != null;ParsedStringTerms brands = aggregations.get("brands");//获取桶brandsbrands.getBuckets().forEach(bucket->{//获取桶中的key 与 记录数System.out.println(bucket.getKeyAsString()+" "+bucket.getDocCount());//获取嵌套的桶price_avgParsedAvg price_avg = bucket.getAggregations().get("price_avg");System.out.println(price_avg.getValue());});
}