文章目录
第3章 搜索解决方案-1 elasticsearch
学习目标:
理解elasticsearch索引结构和数据类型,掌握IK分词器的使用
掌握索引的常用操作(使用Kibana工具)
掌握javaRest高级api
完成数据批量导入
项目序列-11:https://github.com/Jonekaka/javaweb-qingcheng-11-86
1 走进ElasticSearch
1.1 全文检索
1.1.1 为什么要使用全文检索
用户访问我们的首页,一般都会直接搜索来寻找自己想要购买的商品。
需要保证迅速而正确,但是传统数据库数目众多而无分词功能,
常见的全文检索技术有 Lucene、solr 、elasticsearch 等。
1.1.2 理解索引结构
索引是全文检索的核心与原理
下图是索引结构,就像一本书的目录一样
下边是物理结构,上边是逻辑结构
逻辑结构部分是一个倒排索引表:
1、将要搜索的文档内容分词,所有不重复的词组成分词列表。
2、将搜索的文档最终以Document方式存储起来。
3、每个词和docment都有关联。
如下:
现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:
两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配
词条数量的简单 相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个
文档比第二个文档更佳。
1.2 Elasticsearch
1.2.1 Elasticsearch简介
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。
官方网址:https://www.elastic.co/cn/products/elasticsearch
Github:https://github.com/elastic/elasticsearch
优点:
(1)可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据,服务大公
司;也可以运行在单机上
(2)将全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的ES;
(3)开箱即用的,部署简单
(4)全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理
下表是Elasticsearch与MySQL数据库逻辑结构概念的对比
可以增删改查但是具备nosql的特性
1.2.2 安装与启动
下载ElasticSearch 6.5.2版本
https://www.elastic.co/downloads/past-releases/elasticsearch-6.5.2
无需安装,解压安装包后即可使用
配置,依赖包,日志,模块,插件
在命令提示符下,进入ElasticSearch安装目录下的bin目录,执行命令
即可启动。
我们打开浏览器,在地址栏输入http://127.0.0.1:9200/ 即可看到输出结果
版本 “number” : “6.5.2”,所以来的lucence版本"lucene_version" : “7.5.0”,
{
"name" : "DpUGo1b",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "1KYrJiyjRX2EjnS4y0157A",
"version" : {
"number" : "6.5.2",
"build_flavor" : "default",
"build_type" : "zip",
"build_hash" : "9434bed",
"build_date" : "2018-11-29T23:58:20.891072Z",
"build_snapshot" : false,
"lucene_version" : "7.5.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
1.3 使用Postman操作索引库
elasticsearch提供了很多接口
可以在不建立结构的情况下假如数据,nosql,添加数据后自动创建类型,结构,索引
1.3.1 新建文档
/testindex索引如果存在就查询,不存在就创建
/doc索引下的类型,不存在则创建
使用postman测试:以post方式提交 http://127.0.0.1:9200/testindex/doc
body:
{
"name":"测试商品",
"price":123
}
返回结果如下:
索引建立,类型建立,id自动创建,版本建立(如果修改则自动增加),结果类型:创建
{
"_index": "testindex",
"_type": "doc",
"_id": "HIFYSnkBFyCwHiSwwdpJ",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
_id是由系统自动生成的。 为了方便之后的演示,我们再次录入几条测试数据。
输入数据如下:
{
"name":"苹果手机",
"price":789
}
{
"name":"华为手机",
"price":456
}
{
"name":"苹果电脑",
"price":1000
}
1.3.2 查询文档
查询某索引某类型的全部数据,以get方式请求
http://127.0.0.1:9200/testindex/doc/_search 返回结果如下:
“total”: 5,总数
“max_score”: 1,最大分值,那个数据与期望匹配程度最高
“_score”: 1,代表搜索的匹配程度,关键字搜索使用
{
"took": 10,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 5,
"max_score": 1,
"hits": [
{
"_index": "testindex",
"_type": "doc",
"_id": "HoFbSnkBFyCwHiSwHdq7",
"_score": 1,
"_source": {
"name": "苹果手机",
"price": 789
}
},
{
"_index": "testindex",
"_type": "doc",
"_id": "HIFYSnkBFyCwHiSwwdpJ",
"_score": 1,
"_source": {
"name": "测试商品",
"price": 123
}
},
{
"_index": "testindex",
"_type": "doc",
"_id": "IIFfSnkBFyCwHiSwaNpJ",
"_score": 1,
"_source": {
"name": "小米电脑",
"price": 1111
}
},
{
"_index": "testindex",
"_type": "doc",
"_id": "HYFaSnkBFyCwHiSw4Nrz",
"_score": 1,
"_source": {
"name": "华为手机",
"price": 456
}
},
{
"_index": "testindex",
"_type": "doc",
"_id": "H4FcSnkBFyCwHiSwHNrT",
"_score": 1,
"_source": {
"name": "苹果电脑",
"price": 1000
}
}
]
}
}
1.4 映射与数据类型
映射(Mapping)相当于数据表的表结构。
ElasticSearch中的映射(Mapping)用来定义一个文档,可以定义所包含的字段以及字段的类型、分词器及属性等等。
映射可以分为动态映射和静态映射。
动态映射 (dynamic mapping):在关系数据库中,需要事先创建数据库,然后在该数据库实例下创建数据表,然后才能在该数据表中插入数据。而ElasticSearch中不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。
静态映射 :在ElasticSearch中也可以事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射。
常用类型如下:
1.4.1 字符串类型
两者都是 用来存储文本的,但是作用不同
text需要被分析,而keyword已经属于原子类型
1.4.2 整数类型
1.4.3 浮点类型
1.4.4 date类型
日期类型表示格式可以是以下几种:
(1)日期格式的字符串,比如 “2018-01-13” 或 “2018-01-13 12:10:30”
(2)long类型的毫秒数( milliseconds-since-the-epoch,epoch就是指UNIX诞生的UTC
时间1970年1月1日0时0分0秒)
(3)integer的秒数(seconds-since-the-epoch)
1.4.5 boolean类型
逻辑类型(布尔类型)可以接受true/false
1.4.6 binary类型
二进制字段是指用base64来表示索引中存储的二进制数据,可用来存储二进制形式
的数据,例如图像。默认情况下,该类型的字段只存储不索引。二进制类型只支持
index_name属性。
1.4.7 array类型
在ElasticSearch中,没有专门的数组(Array)数据类型,但是,在默认情况下,任
意一个字段都可以包含0或多个值,这意味着每个字段默认都是数组类型,只不过,数组
类型的各个元素值的数据类型必须相同。在ElasticSearch中,数组是开箱即用的(out of
box),不需要进行任何配置,就可以直接使用。
在同一个数组中,数组元素的数据类型是相同的,ElasticSearch不支持元素为多个
数据类型:[ 10, “some string” ],常用的数组类型是:
(1)字符数组: [ “one”, “two” ]
(2)整数数组: productid:[ 1, 2 ]
(3)对象(文档)数组: “user”:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”:10 }],ElasticSearch内部把对象数组展开为 {“user.name”: [“Mary”, “John”], “user.age”:[12,10]}
1.4.8 object类型
JSON天生具有层级关系,文档会包含嵌套的对象
比如上次的菜单,可以直接存储为树状json
1.5 IK分词器
1.5.1 什么是IK分词器
使用postman测试 post方式提交 http://127.0.0.1:9200/testindex/_analyze
{"analyzer": "chinese", "text": "我是中国人" }
浏览器返回效果如下
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "中",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "国",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
},
{
"token": "人",
"start_offset": 4,
"end_offset": 5,
"type": "<IDEOGRAPHIC>",
"position": 4
}
]
}
默认的中文分词是将每个字看成一个词,这显然是不符合要求的,所以我们需要安装中
文分词器来解决这个问题。
IK分词是一款国人开发的相对简单的中文分词器。虽然开发者自2012年之后就不在维护
了,但在工程应用中IK算是比较流行的一款!我们今天就介绍一下IK中文分词器的使用。
1.5.2 IK分词器安装
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases 下载6.5.2版
(1)先将其解压,将解压后的elasticsearch文件夹重命名文件夹为ik
(2)将ik文件夹拷贝到elasticsearch/plugins 目录下。
(3)重新启动,即可加载IK分词器
1.5.3 IK分词器测试
IK提供了两个分词算法ik_smart 和 ik_max_word
其中 ik_smart 为最少切分,ik_max_word为最细粒度划分
我们分别来试一下
(1)最小切分:
使用postman测试 post方式提交 http://127.0.0.1:9200/testindex/_analyze
{"analyzer": "ik_smart", "text": "我是中国人" }
输出的结果为:
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "中国人",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 2
}
]
}
(2)最细切分:
使用postman测试 post方式提交 http://127.0.0.1:9200/testindex/_analyze
{“analyzer”: “ik_max_word”, “text”: “我是中国人” }
输出的结果为:
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "中国人",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 2
},
{
"token": "中国",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 3
},
{
"token": "国人",
"start_offset": 3,
"end_offset": 5,
"type": "CN_WORD",
"position": 4
}
]
}
1.5.4 自定义词库
我们现在测试"龙云播客",结果如下:
{“analyzer”: “ik_max_word”, “text”: “龙云播客” }
{
"tokens": [
{
"token": "龙",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "云",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "播",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 2
},
{
"token": "客",
"start_offset": 3,
"end_offset": 4,
"type": "CN_CHAR",
"position": 3
}
]
}
默认的分词并没有识别“龙云播客”是一个词。如果我们想让系统识别“龙云播客”是一个
词,需要编辑自定义词库。
步骤:
(1)进入elasticsearch/plugins/ik/config目录
(2)新建一个my.dic文件,格式另存为UTF-8,编辑内容:
龙云播客
修改IKAnalyzer.cfg.xml(在ik/config目录下)
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!‐‐用户可以在这里配置自己的扩展字典 ‐‐>
<entry key="ext_dict">my.dic</entry>
<!‐‐用户可以在这里配置自己的扩展停止词字典‐‐>
<entry key="ext_stopwords"></entry>
</properties>
重新启动elasticsearch,通过浏览器测试分词效果
{
"tokens": [
{
"token": "龙云播客",
"start_offset": 0,
"end_offset": 4,
"type": "CN_WORD",
"position": 0
}
]
}
1.6 Kibana
1.6.1 Kibana简介
Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。
Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析,并在各种图表、表格和地图中可视化数据。
1.6.2 Kibana安装与启动
(1)解压 资料\配套软件\elasticsearch\kibana-6.5.2-windows-x86_64.zip
(2)如果Kibana远程连接Elasticsearch ,可以修改config\kibana.yml
(3)执行bin\kibana.bat
(4)打开浏览器,键入http://localhost:5601 访问Kibana
我们这里使用Kibana进行索引操作,Kibana与Postman相比省略了服务地址,并且有语
法提示,非常便捷。
2 索引操作
这里是静态映射,先创建表结构
为什么要静态映射呢?
因为为索引字段可以自定义功能,比如分词器,动态映射不具备此功能
2.1 创建索引与映射字段
语法
请求方式依然是PUT
类型名称:就是前面将的type的概念,类似于数据库中的不同表
字段名:任意命名,可以指定许多属性,来表示字段,如:
type:类型,可以是text、long、short、date、integer、object等
index:是否索引,默认为true,需要排序或者查询可以设置。图片不需要查询,存储就行
store:是否单独存储,默认为false ,一般内容比较多的字段设置成true,可提升查询性能
analyzer:指定分词器
PUT /索引库名
{
"mappings": {
"类型名称":{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
}
分析需求:
对商品页分析需要查询的参数
这里的id就用sku的id代替
示例
发起请求:
#创建索引结构
PUT sku
{
"mappings":{
"doc":{
"properties":{
"name":{
"type":"text",
"analyzer":"ik_smart"
},
"price":{
"type":"integer"
},
"image":{
"type":"text"
},
"createTime":{
"type":"date"
},
"spuId":{
"type":"text"
},
"categoryName":{
"type":"keyword"
},
"brandName":{
"type":"object"
},
"saleNum":{
"type":"integer"
},
"commentNum":{
"type":"integer"
}
}
}
}
}
响应结果:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "sku"
}
2.2 文档增加与修改
2.2.1 增加文档自动生成ID
通过POST请求,可以向一个已经存在的索引库中添加数据。
语法:
POST 索引库名/类型名
{
"key":"value"
}
示例:
POST sku/doc
{
"name":"小米手机",
"price":1000,
"spuId":"101",
"createTime":"2019‐03‐01",
"categoryName":"手机",
"brandName":"小米",
"saleNum":10102,
"commentNum":1331,
"spec":{
"网络制式":"移动4G",
"屏幕尺寸":"4.5"
}
}
响应:
{
"_index": "sku",
"_type": "doc",
"_id": "hyjXKWkBtgZXp‐‐BshX_",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
通过以下命令查询sku索引的数据
GET sku/_search
响应结果如下:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "sku",
"_type": "doc",
"_id": "hyjXKWkBtgZXp‐‐BshX_",
"_score": 1,
"_source": {
"name": "小米手机",
"price": 1000,
"spuId": 1010101011,
"createTime": "2019‐03‐01",
"categoryName": "手机",
"brandName": "小米",
"saleNum": 10102,
"commentNum": 1331,
"spec": {
"网络制式": "移动4G",
"屏幕尺寸": "4.5"
}
}
}
]
}
}
2.2.2 新增文档指定ID
如果我们想要自己新增的时候指定id,可以这么做:
语法
PUT /索引库名/类型/id值
{
...
}
示例:
PUT sku/doc/1
{
"name": "小米电视",
"price": 1000,
"spuId": 1010101011,
"createTime": "2019‐03‐01",
"categoryName": "电视",
"brandName": "小米",
"saleNum": 10102,
"commentNum": 1331,
"spec": {
"网络制式": "移动4G",
"屏幕尺寸": "4.5"
}
}
响应:
{
"_index": "sku",
"_type": "doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
再次查询,观察返回的结果。
2.3 索引查询
基本语法
GET /索引库名/_search
{
"query":{
"查询类型":{
"查询条件":"查询条件值"
}
}
}
其实修改也一样,只需要修改其中的某个数据,自动识别为update
不写条件,默认查询全部
这里的query代表一个查询对象,里面可以有不同的查询属性
查询类型:
例如: match_all , match , term , range 等等
查询条件:查询条件会根据类型的不同,写法也有差异,后面详细讲解
为了看到清晰的测试效果,我们再录入两条数据:三星手机和三星电视
2.3.1 查询所有数据(match_all)
示例:
#查询所有
GET /sku/_search
{
"query": {
"match_all": {}
}
}
query :代表查询对象
match_all :代表查询所有
took:查询花费时间,单位是毫秒
time_out:是否超时
_shards:分片信息
hits:搜索结果总览对象
total:搜索到的总条数
max_score:所有结果中文档得分的最高分
hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
_index:索引库
_type:文档类型
_id:文档id
_score:文档得分
_source:文档的源数据
2.3.2 匹配查询(match)
示例:查询名称包含手机的记录
GET /sku/doc/_search
{
"query": {
"match": {
"name": "手机"
}
}
}
结果:查询出三星手机和小米手机两条结果
如果查询名称包含电视的,结果为三星电视和小米电视两条结果
如果我们查询“小米电视”会有几条记录被查询出来呢?
猜测有一条,但结果为小米电视、三星电视、小米手机三条结果,
这是为什么呢?这是因为在查询时,会先将搜索关键字进行分词,对分词后的字符串进行查询,只要是包含这些字符串的都是要被查询出来的,多个词之间是 or 的关系。
返回的结果中_score是对这条记录的评分,评分代表这条记录与搜索关键字的匹配度,
查询结果按评分进行降序排序。 比如我们刚才搜索“小米电视” ,那小米电视这条记录的
评分是最高的,排列在最前面。
往往在查询时用户需要更多的结果,符合人性化
如果我们需要精确查找,也就是同时包含小米和电视两个词的才可以查询出来,这就需
要将操作改为 and 关系:
GET /sku/doc/_search
{
"query": {
"match": {
"name": {
"query": "小米电视",
"operator": "and"
}
}
}
}
查询结果为小米电视一条记录了。
2.3.3 多字段查询(multi_match)
multi_match 与 match类似,不同的是它可以在多个字段中查询
GET /sku/_search
{
"query": {
"multi_match": {
"query": "小米",
"fields": [
"name",
"brandName",
"categoryName"
]
}
}
}
本例中,我们会在name、brandName 、categoryName字段中查询 小米 这个词
2.3.4 词条匹配(term)
term查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字
符串
GET /sku/_search
{
"query": {
"term": {
"price": 1000
}
}
}
2.3.5 多词条匹配(terms)
terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定
值中的任何一个值,那么这个文档满足条件:
GET /sku/_search
{
"query": {
"terms": {
"price": [
1000,
2000,
3000
]
}
}
}
2.3.6 布尔组合(bool)
bool 把各种其它查询通过 must (与)、 must_not (非)、 should (或)的方式进行
组合
示例:查询名称包含手机的,并且品牌为小米的。
GET /sku/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "手机"
}
},
{
"term": {
"brandName": "小米"
}
}
]
}
}
}
示例:查询名称包含手机的,或者品牌为小米的。
GET /sku/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "手机"
}
},
{
"term": {
"brandName": "小米"
}
}
]
}
}
}
2.3.7 过滤查询
过滤是针对搜索的结果进行过滤,过滤器主要判断的是文档是否匹配,不去计算和判断文档的匹配度得分,所以过滤器性能比查询要高,且方便缓存,推荐尽量使用过滤器去实现查询或者过滤器和查询共同使用。
示例:过滤品牌为小米的记录
GET /sku/_search
{
"query": {
"bool": {
"filter": [
{
"match": {
"brandName": "小米"
}
}
]
}
}
}
2.3.8 分组查询
示例:按分组名称聚合查询,统计每个分组的数量
aggregation聚合
terms查询的类型
sku_category自定义分组结果集的名称
categoryName按照其分组
GET /sku/_search
{
"size": 0,
"aggs": {
"sku_category": {
"terms": {
"field": "categoryName"
}
}
}
}
size为0 不会将数据查询出来,目的是让查询更快。
我们也可以一次查询两种分组统计结果:
#分组查询
GET sku/_search
{
"size": 0,
"aggs": {
"sku_category": {
"terms": {
"field": "categoryName"
}
},
"sku_brand": {
"terms": {
"field": "brandName"
}
}
}
}
3 JavaRest 高级客户端入门
3.1 JavaRest 高级客户端简介
将界面操作,转化为代码自动操作
elasticsearch 存在三种Java客户端。
- Transport Client
- Java Low Level Rest Client(低级rest客户端)
- Java High Level REST Client(高级rest客户端)
这三者的区别是:
TransportClient没有使用RESTful风格的接口,而是二进制的方式传输数据。tcp/ip,废弃
ES官方推出了Java Low Level REST Client,它支持RESTful。但是缺点是因为TransportClient的使用者把代码迁移到Low Level REST Client的工作量比较大。
ES官方推出Java High Level REST Client,它是基于Java Low Level REST Client的封装,并且API接收参数和返回值和TransportClient是一样的,使得代码迁移变得容易
并且支持了RESTful的风格,兼容了这两种客户端的优点。强烈建议ES5及其以后的版本使用Java High Level REST Client。
准备工作:新建工程,引入依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch‐rest‐high‐level‐client</artifactId>
<version>6.5.3</version>
</dependency>
3.2 快速入门
3.2.1 新增和修改数据
插入单条数据:
HttpHost : url地址封装
RestClientBuilder: rest客户端构建器
RestHighLevelClient: rest高级客户端
IndexRequest: 新增或修改请求
IndexResponse:新增或修改的响应结果
批处理请求:可以for循环,但是需要和服务器多次交互,没有必要
BulkRequest: 批量请求(用于增删改操作)使用,和查询区别开
BulkResponse:批量请求(用于增删改操作)
public class Test1 {
public static void main(String[] args) throws IOException {
/*连接rest接口*/
/*获得需要连接的地址*/
HttpHost http = new HttpHost("localhost", 9200, "http");
/*创建rest客户端构建器*/
RestClientBuilder restClientBuilder = RestClient.builder(http);
/*创建高级客户端rest*/
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
/*封装请求对象*/
/*索引,文档名,id*/
BulkRequest bulkRequest=new BulkRequest();
IndexRequest indexRequest = new IndexRequest("sku", "doc", "100");
Map skuMap=new HashMap();
skuMap.put("name","华为mete20 pro");
skuMap.put("brandName","华为");
skuMap.put("categoryName","手机");
skuMap.put("price",1010221);
skuMap.put("createTime","2019-05-01");
skuMap.put("saleNum",101021);
skuMap.put("commentNum",10102321);
Map spec=new HashMap();
spec.put("网络制式","移动4G");
spec.put("屏幕尺寸","5");
skuMap.put("spec",spec);
indexRequest.source(skuMap);
/*获得执行结果*/
/*有一个默认参数,暂时没用,只是为了以后扩展*/
bulkRequest.add(indexRequest);
//IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
int status = bulkResponse.status().getStatus();
System.out.println(status);
/*客户端有始有终*/
client.close();
}
}
如果ID不存在则新增,如果ID存在则修改。
3.2.2 匹配查询
SearchRequest: 查询请求对象
SearchResponse:查询响应对象
SearchSourceBuilder:查询源构建器,将携带参数的查询请求对象构建,就是吸收上述请求与相应对象的数据,构建json数据对象
MatchQueryBuilder:匹配查询构建器
示例:查询商品名称包含电视的记录。
public class Test2 {
public static void main(String[] args) throws IOException {
/*连接rest接口*/
/*获得需要连接的地址*/
HttpHost http = new HttpHost("localhost", 9200, "http");
/*创建rest客户端构建器*/
RestClientBuilder restClientBuilder = RestClient.builder(http);
/*创建高级客户端rest*/
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
/*封装请求对象*/
/*GET sku/doc/_search
{
"query":
{
"match":{
"name":{
"query":"电视"
}
}
}
}*/
/*查询请求,get sku*/
SearchRequest searchRequest = new SearchRequest("sku");
/*get sku/doc,不写默认查询sku的全部索引*/
searchRequest.types("doc");
/*json信息,query,查询源构建器"query":*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/*json,match属性"match":*/
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "电视");
/*match在query内*/
searchSourceBuilder.query(matchQueryBuilder);
/*query在打括号内,作为一个完整的请求体,模拟手写json的功能*/
searchRequest.source(searchSourceBuilder);
/*使用高级客户端获取查询结果*/
/*开头的命令/_search,将大括号里面的json装载进入*/
/*RequestOptions.DEFAULT,只有一个选项,并没有特殊的意义,只是为了以后的扩展*/
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
/*查询结果都在hits里面*/
/*{
"took" : 42,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "sku",
"_type" : "doc",
"_id" : "1",
"_score" : 0.5753642,
"_source" : {
"name" : "小米电视",
"price" : 1000,
"spuId" : 1010101011,
"createTime" : "2019‐03‐01",
"categoryName" : "电视",
"brandName" : "小米",
"saleNum" : 10102,
"commentNum" : 1331,
"spec" : {
"网络制式" : "移动4G",
"屏幕尺寸" : "4.5"
}
}
}
]
}
}*/
/*然而hits有两层,因此继续寻找*/
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits();
System.out.println("记录数" + totalHits);
/*得到内容hits列*/
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
System.out.println(source);
}
/*客户端有始有终*/
client.close();
}
}
3.2.3 布尔与词条查询
BoolQueryBuilder:布尔查询构建器
TermQueryBuilder:词条查询构建器
QueryBuilders:查询构建器工厂
示例:查询名称包含手机的,并且品牌为小米的记录
//1.连接rest接口 同上......
//2.封装查询请求
//3.获取查询结果 同上......
public class Test3 {
public static void main(String[] args) throws IOException {
/*连接rest接口*/
/*获得需要连接的地址*/
HttpHost http = new HttpHost("localhost", 9200, "http");
/*创建rest客户端构建器*/
RestClientBuilder restClientBuilder = RestClient.builder(http);
/*创建高级客户端rest*/
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
/*GET /sku/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "电视"
}
},
{
"term": {
"brandName": "小米"
}
}
]
}
}
}*/
/*封装请求对象*/
SearchRequest searchRequest = new SearchRequest("sku");
searchRequest.types("doc");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/*"bool": {*/
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
/*match": {
"name": "电视"
}*/
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "电视");
/*"must": [
{
"match": {
"name": "电视"
}
},*/
boolQueryBuilder.must(matchQueryBuilder);
/*term查询,类似*/
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", "小米");
/*加入布尔,must*/
boolQueryBuilder.must(termQueryBuilder);
/*bool加入query*/
searchSourceBuilder.query(boolQueryBuilder);
/*query加入search*/
searchRequest.source(searchSourceBuilder);
/*获得请求对象*/
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits();
System.out.println("记录数" + totalHits);
/*得到内容hits列*/
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
System.out.println(source);
}
/*客户端有始有终*/
client.close();
}
}
3.2.4 过滤查询
示例:筛选品牌为小米的记录
//1.连接rest接口 同上.....
//2.封装查询请求
//设置查询的类型
//布尔查询构建器
//3.获取查询结果 同上....
public class Test4 {
public static void main(String[] args) throws IOException {
/*连接rest接口*/
/*获得需要连接的地址*/
HttpHost http = new HttpHost("localhost", 9200, "http");
/*创建rest客户端构建器*/
RestClientBuilder restClientBuilder = RestClient.builder(http);
/*创建高级客户端rest*/
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
/*封装请求对象*/
/*GET sku/_search
{
"query":{
"bool":{
"filter": [
{
"match":
{
"brandName":"小米"
}
}
]
}
}
}*/
SearchRequest searchRequest = new SearchRequest("sku");
searchRequest.types("doc");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
/*构建match条件*/
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", "小米");
/*bool的filter吸收match,可以吸收多个*/
boolQueryBuilder.filter(termQueryBuilder);
/*query吸收bool*/
searchSourceBuilder.query(boolQueryBuilder);
/*_search吸收query*/
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
/*然而hits有两层,因此继续寻找*/
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits();
System.out.println("记录数" + totalHits);
/*得到内容hits列*/
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
System.out.println(source);
}
/*客户端有始有终*/
client.close();
}
}
3.2.5 分组(聚合)查询
AggregationBuilders:聚合构建器工厂
TermsAggregationBuilder:词条聚合构建器
Aggregations:分组结果封装
Terms.Bucket: 桶
示例:按商品分类分组查询,求出每个分类的文档数
其实就是模拟json,得到数据json,再从json中提取值
//rest构建器
//高级客户端对象
//2.封装查询请求
//设置查询的类型
//3.获取查询结果
public class Test5 {
public static void main(String[] args) throws IOException {
/*连接rest接口*/
/*获得需要连接的地址*/
HttpHost http = new HttpHost("localhost", 9200, "http");
/*创建rest客户端构建器*/
RestClientBuilder restClientBuilder = RestClient.builder(http);
/*创建高级客户端rest*/
RestHighLevelClient client = new RestHighLevelClient(restClientBuilder);
/*封装请求对象*/
/*GET sku/_search
{
"size": 0,
"aggs": {
"sku_category": {
"terms": {
"field": "categoryName"
}
},
"sku_brand": {
"terms": {
"field": "brandName"
}
}
}
}*/
SearchRequest searchRequest = new SearchRequest("sku");
searchRequest.types("doc");
//query
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//分组
TermsAggregationBuilder termsAggregationBuilder =
AggregationBuilders.terms("sku_category").field("categoryName");
//agg
searchSourceBuilder.aggregation(termsAggregationBuilder);
//不输出全部的结果集
searchSourceBuilder.size(0);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
/*从json返回值提取数据,因此继续寻找*/
Aggregations aggregations = searchResponse.getAggregations();
//返回map集合
Map<String, Aggregation> asMap = aggregations.getAsMap();
//得到map中sku_category键值对
Terms terms = (Terms) asMap.get("sku_category");
//得到桶中的内容
List<? extends Terms.Bucket> buckets = terms.getBuckets();
for (Terms.Bucket bucket : buckets) {
System.out.println(bucket.getKeyAsString()+":"+bucket.getDocCount());
}
/*客户端有始有终*/
client.close();
}
}
原始json
{
"took" : 16,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"sku_category" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "电视",
"doc_count" : 2
},
{
"key" : "手机",
"doc_count" : 1
}
]
},
"sku_brand" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "小米",
"doc_count" : 2
},
{
"key" : "华为",
"doc_count" : 1
}
]
}
}
}
4 批量数据导入
4.1 需求分析
单独建立工程,实现青橙商品索引数据的批量导入。
4.2 实现思路
(1)建立工程,引入通用mapper 和elasticsearch依赖
(2)查询sku表数据,将集合数据循环插入到elasticsearch中