一、引言
1.1 海量数据
在海量数据中执行搜索功能时,如果使用MySQL,效率太低
1.2 全文检索
在海量数据中执行搜索功能时,如果使用MySQL,效率太低
1.3 高亮显示
我想用红色字体显示搜索关键字和亮点
elasticsearch基本操作(ES7.x入门)_小诸葛博客博客-CSDN博客_es7 分组查询..%22%7D&request_id=165816120716780366598284&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-123939544-null-null.142v32new_blog_pos_by_title,185v2control&utm_term=es 7.x&spm=1018.2226.3001.4187
二、ES概述
2.1 ES的介绍
ES是一个使用( Apache Lucene - Welcome to Apache Lucene/ )他提供了分布式全文搜索功能统一的搜索引擎框架RESTful风格的WEB接口和官方客户端也为各种语言提供了相应的服务API支持
Lucene:Lucene搜索引擎本身就是底层
分布式:ES主要是突出他的横向扩展能力
全文搜索:将单词分成单词,并将单个单词统一放入单词库。搜索时,根据关键字搜索单词库,找到匹配的内容(倒置索引,以下解释)
RESTful风格的WEB接口:操作ES很简单,只需要发送一个HTTP根据不同的请求模式和不同的携带参数,可以执行相应的功能
应用广泛:Github.com,WIKI,Gold Man等用ES,每天维护近10次TB的数据
更详细的介绍,可以百度搜索es官方文件或中文文件权威指南等↓
1Getting Started | Elasticsearch Reference [6.0] | Elastic
2Elasticsearch: 权威指南 | Elastic
2.2 ES的由来
ES回忆时光 |
---|
##
2.3 ES和Solr
Solr查询死数据时,速度相对ES更快,但如果数据实时更改,Solr查询速度会大大降低,ES查询效率基本不变
Solr建筑需要依赖Zookeeper帮助管理,而ES支持集群建设,不需要第三方介入
最开始Solr社区可以说很受欢迎,但国内文件不多,在ES出现之后,ES社区人气直线上升,ES文档非常健全
ES对现在的云计算和大数据也支持的特别好
2.4 倒排索引
详细介绍倒排索引↓
假设有两篇文章1号和2号:
文章1的内容是:老超在卡门工作,我也是。
文章2的内容是:小超在鼓楼工作。
首先,获得这两篇文章的关键词。如果我们把文章看作一个字符串,我们需要在字符串中获得所有的单词,即分词。在分词时,忽略在、和其他毫无意义的介词,以及可以过滤的标点符号。
文章1号 |
---|
文章2号 |
---|
接下来,有了关键词,我们就可以建立倒排索引了。以上对应关系是:文章号对文章中的所有关键词。倒排索引将这种关系转换为: 关键词对拥有关键词的所有文章号。
文章1,文章2倒排后变成 |
---|
通常只知道哪些文章中出现关键词是不够的,我们还需要知道文章中出现关键词的次数和位置,通常有两个位置:
a.字符位置,即记录文章中的第几个字符(优点是关键字明显时定位快)
b.关键词位置,即记录这个词是文章中的第一个关键词(优点是节省索引空间和短语(phase)查询快)
加上频率和位置信息,我们的索引结构变成了以下几种方式↓ |
---|
说白了,倒排索引就是把搜索的关键词提取出来给索引,而不是像以前那样和所有其他内容逐一比较,就像查字典一样↑
三、 ElasticSearch安装
3.1 安装ES&Kibana
下面启动es,无法访问地址栏输入地址,查看容器启动日志,发现,es报ERROR,
所以要做以下修改↓
这里的配置需要切换修改root用户需要在这里修改/etc/sysctl.conf为了完成设置,修改本文件的最大过程是es要求的值
root用户修改配置sysctl.conf
vim /etc/sysctl.conf
添加以下配置
vm.max_map_count=655360
执行命令
sysctl -p
重新启动容器
docker-compose.yml
version: "3.1" services: elasticsearch: image: daocloud.io/library/elasticsearch:6.5.4 restart: always container_name: elasticsearch ports: - 9200:9200 kibana: image: daocloud.io/library/kibana:6.5.4 restart: always container_name: kibana ports: - 5601:5601 environment: - elasticsearch_url=http://10.20.159.25:9200 depends_on: - elasticsearch
上面的depends_on:指的是kb依赖以下指定es服务
流程如下↓
1进入/opt文件夹创建yml然后编辑文件,复制笔记配置内容,启动容器↓
上述代码如下↓
[root@localhost ~]# cd /opt [root@localhost opt]# ls containerd docker_mysql_tomcat docker_nginx docker_nginx_cluster docker_redis [root@localhost opt]# mkdir docker_es [root@localhost opt]# cd docker_es/ [root@localhost docker_es]# vi docker-compose.yml [root@localhost docker_es]# ls docker-compose.yml [root@localhost docker_es]# docker-composeup -d2输入Linux服务器的ip和es端口9200,测试es服务器是否安装成功,发现输入地址es服务器死活访问不了,↓
通过输入编排日志们命令加上-f参数来查看容器启动日志,看看es服务器是否启动成功,
通过查看日志发现,es报ERROR了,所以要做上面一开头的修改↕
上面报错看开头也行,或者这里直接操作下面的代码↓
[root@localhost ~]# vi /etc/sysctl.conf # sysctl.conf配置文件打开都是注释,增加配置如下↓ vm.max_map_count=655360 # 编辑完上面文件保存退出,执行下面命令sysctl -p↓ [root@localhost ~]# sysctl -p vm.max_map_count = 655360 # 最后重启容器后,稍等一点时间,就可以去访问es服务器了↓ [root@localhost ~]# cd/opt/docker_es/ [root@localhost docker_es]# ls docker-compose.yml [root@localhost docker_es]# docker-compose restart3输入Linux服务器的ip和es图形化客户端kb端口5601,测试kb是否安装成功↓
3.2 安装IK分词器
es默认的分词器对中文分词不太友好,所以要安装一个对中文分词友好的安装IK分词器↓
下载IK分词器的地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip
由于网络问题采用国内的路径去下载↓
进去到ES容器内部,跳转到bin目录下,执行bin目录下的脚本文件:
./elasticsearch-plugin install http://tomcat01.qfjava.cn:81/elasticsearch-analysis-ik-6.5.4.zip
重启ES的容器,让IK分词器生效。
流程如下↓
1进入es容器内部,找到bin目录里面的插件文件夹,用插件命令访问http地址去安装ik分词器↓
2安装完要确认y,然后记住要重启es容器才能让插件生效,这点跟idea安装lombok插件重启巧记理解即可↓
3打开kb,测试效果,分词器analyzer赋值为ik最大分词ik_max_word,写法不会可以百度或者github搜索↓
校验IK分词器 |
---|
默认使用 "analyzer": "standard"标准分词器
上面的文字不会,直接打有部分提示,或者抄下面即可↓ POST _analyze { "analyzer": "ik_max_word", "text": "我是中国人" }
四、 ElasticSearch基本操作
4.1 ES的结构
4.1.1 索引Index,分片和备份
索引是ElasticSearch存放数据的地方,可以理解为关系型数据库中的一个数据库。事实上,我们的数据被存储和索引在分片(shards)中,索引只是一个,把一个或多个分片分组在一起的逻辑空间。然而,这只是一些内部细节——我们的程序完全不用关心分片。对于我们的程序而言,文档存储在索引(index)中。剩下的细节由Elasticsearch关心既可()
ES的服务中,可以创建多个索引。
每一个索引默认被分成5片存储。
每一个分片都会存在至少一个备份分片。
备份分片默认不会帮助检索数据,当ES检索压力特别大的时候,备份分片才会帮助检索数据。
备份的分片必须放在(不要把所有的鸡蛋都放在同一个篮子的道理)。
索引分片备份 |
---|
4.1.2 类型 Type
类型用于区分同一个索引下不同的数据类型,相当于关系型数据库中的表。在Elasticsearch中,我们使用相同类型(type)的文档表示相同的“事物”,因为他们的数据结构也是相同的。每个类型(type)都有自己的映射(mapping)或者结构定义,就像传统数据库表中的列一样。所有类型下的文档被存储在同一个索引下,但是类型的映射(mapping)会告诉Elasticsearch不同的文档如何被索引。
es 6.0 开始不推荐一个index下多个type的模式,并且会在 7.0 中完全移除。在 6.0 的index下是无法创建多个type的
Ps:根据版本不同,类型的创建也不同。
类型 |
---|
4.1.3 文档 Doc
文档是ElasticSearch中存储的实体,类比关系型数据库,每个文档相当于数据库表中的一行数据。 在Elasticsearch中,文档(document)这个术语有着特殊含义。它特指最顶层结构或者根对象(root object)序列化成的JSON数据(以唯一ID标识并存储于Elasticsearch中)。
一个类型下,可以有多个文档。这个文档就类似于MySQL表中的多行数据。
文档 |
---|
4.1.4 属性 Field
一个文档中,可以包含多个属性。类似于MySQL表中的一行数据存在多个列。
属性 |
---|
4.2 操作ES的RESTful语法
GET请求:
http://ip:port/index:查询索引信息
http://ip:port/index/type/doc_id:查询指定的文档信息
POST请求:
http://ip:port/index/type/_search:搜索文档,可以在请求体中提交json字符串来代表查询条件
http://ip:port/index/type/doc_id/_update:更新文档,请求体中提交json字符串代表修改的具体信息
PUT请求:
http://ip:port/index:放置或者说创建一个索引,需要在请求体中指定索引的信息,类型,结构
http://ip:port/index/type/_mappings:放置或者说创建索引时,指定索引文档存储的属性的信息
DELETE请求:
http://ip:port/index:删除索引
http://ip:port/index/type/doc_id:删除对应id的文档
4.3 索引的操作
4.3.1 创建一个索引
语法如下
# 创建一个索引 PUT /person { "settings": { "number_of_shards": 5, "number_of_replicas": 1 } }
结果如下↓
4.3.2 查看索引信息
语法如下
# 查看索引信息 GET /person
查询结果如下↓
还可以点击kb的管理来查看↓
查看信息 |
---|
health:健康的情况,正常的情况下es的健康状态是绿色。 status:状态 Primaries:分片数量 Replicas:备份数量 Docs count:文档数量
这里新建的索引健康状态是黄色是因为es默认会把备份的分片放到其他服务器上面,但是目前我们是单机版找不到其他的es服务器所以是黄色而已。
4.3.3 删除索引
语法如下
# 删除索引 DELETE /person
删除结果如下↓
也可以点击kb的管理来删除↓
4.4 ES中Field可以指定的类型
字符串类型:
text:一般被用于全文检索,将当前Field进行分词。
keyword:搜索关键字,当前Field不会被分词。
数值类型:
long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节
byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节
double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节
float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节
half_float:精度比float小一半。
scaled_float:根据一个long和scaled来表达一个浮点型,long-345,scaled-100 -> 3.45
时间类型:
date类型,针对时间类型指定具体的格式
布尔类型:
boolean类型,表达true和false
二进制类型:
binary类型暂时支持Base64 encode string
范围类型:
long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
integer_range:同上
double_range:同上
float_range:同上
date_range:同上
ip_range:同上
经纬度类型:
geo_point:用来存储经纬度的
ip类型:
ip:可以存储IPV4或者IPV6
其他的数据类型参考官网:Field datatypes | Elasticsearch Guide [6.5] | Elastic
4.5 创建索引并指定数据结构
语法如下
# 创建索引,指定数据结构 PUT /book { "settings": { # 分片数 "number_of_shards": 5, # 备份数 "number_of_replicas": 1 }, # 指定数据结构 "mappings": { # 类型 Type "novel": { # 文档存储的Field "properties": { # Field属性名 "name": { # 类型 "type": "text", # 指定分词器 "analyzer": "ik_max_word", # 指定当前Field可以被作为查询的条件 "index": true , # 是否需要额外存储,一般不需要,可以通过其他关键字比如_source查出来 "store": false }, "author": { "type": "keyword" }, "count": { "type": "long" }, "on-sale": { "type": "date", # 时间类型的格式化方式 "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" }, "descr": { "type": "text", "analyzer": "ik_max_word" } } } } }
PUT /book { "settings": { "number_of_shards": 5, "number_of_replicas": 1 }, "mappings": { "novel": { "properties": { "name": { "type": "text", "analyzer": "ik_max_word", "store": false }, "author": { "type": "keyword" }, "count": { "type": "long" }, "on-sale": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" }, "descr": { "type": "text", "analyzer": "ik_max_word" } } } } }
结果如下↓
4.6 文档的操作
文档是ES服务中的唯一标识,
_index
,_type
,_id
三个内容为组合,锁定一个文档,如果不存在就添加,否则就是修改。
4.6.1 新建文档
自动生成_id,在路径/下不写id,es给你自动生成,写了比如/1就用你指定的id1,很容易理解↓
# 添加文档,自动生成id POST /book/novel { "name": "盘龙", "author": "我吃西红柿", "count": 100000, "on-sale": "2000-01-01", "descr": "山重水复疑无路,柳暗花明又一村" }
结果如下↓
手动指定_id
# 添加文档,手动指定id PUT /book/novel/1 { "name": "红楼梦", "author": "曹雪芹", "count": 10000000, "on-sale": "1985-01-01", "descr": "一个是阆苑仙葩,一个是美玉无瑕" }
结果如下↓
4.6.2 修改文档
覆盖式修改,覆盖所有
# 添加文档,手动指定id,除了添加,第二次就是就是覆盖式修改 PUT /book/novel/1 { "name": "红楼梦", "author": "曹雪芹", "count": 4353453, "on-sale": "1985-01-01", "descr": "一个是阆苑仙葩,一个是美玉无瑕" }
doc修改方式,修改某个
# 修改文档,基于doc方式 POST /book/novel/1/_update { "doc": { "count": "1234565" } }
结果如下↓
查看修改的具体数据,图形化界面步骤↓
1点击管理,在打开界面点击索引匹配↓
2点击创建索引,然后输入索引的名字book,点下一步↓
3点击下拉三角,不知道选什么就选不知道,点下一步↓
4点击发现,就能查到发现的数据的详细结果了↓
5点击发现,就能查到发现的数据的详细结果了↓
4.6.3 删除文档
根据id删除
# 根据id删除文档,下面这个au1kkHoB7Xrpe4LJZRou是之前不指定自动生成的id,如果是手动指定的比如1,就写1 DELETE /book/novel/I8iXnnoB3j5F3DdMxsYB
结果如下↓
点击发现按钮,找到盘龙那条数据的id,执行删除,再回到发现查看,发现数据没了,删除成功↓
1
2
五、Java操作ElasticSearch【重点
】
5.1 Java连接ES
创建Maven工程Java基础工程比如起名叫es↓
查找es和es高级api依赖,打开maven仓库官网搜索即可,点击里面找到和Linux服务器安装的对应版本6.5.4↓
导入依赖↓
<dependencies> <!-- 1. elasticsearch--> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.5.4</version> </dependency> <!-- 2. elasticsearch的高级API--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>6.5.4</version> </dependency> <!-- 3. junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- 4. lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.22</version> </dependency> </dependencies>
创建工具类,用来连接ES服务器
package com.qf.utils; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; public class ESClient { public static RestHighLevelClient getClient(){ // 创建HttpHost对象 HttpHost httpHost = new HttpHost("10.20.100.186",9200);//连接Linux服务器安装的es和ip和端口 // 创建RestClientBuilder RestClientBuilder clientBuilder = RestClient.builder(httpHost); // 创建RestHighLevelClient RestHighLevelClient client = new RestHighLevelClient(clientBuilder); // 返回 return client; } }
测试类调用工具类方法,如果没有报错,表示连上了es服务器↓
package com.qf.utils.test; import com.qf.utils.ESClient; import org.elasticsearch.client.RestHighLevelClient; import org.junit.Test; public class Demo01 { @Test public void testConnect() { RestHighLevelClient client = ESClient.getClient();//.var,org.elasticsearch.client.RestHighLevelClient@61009542 System.out.println(client); System.out.println("测试类调用工具类方法,如果没有报错,表示连上了es服务器"); } }
5.2 Java操作索引
API | 说明 |
---|---|
Settings.Builder | 封装了settings中的信息 |
XContentBuilder | 封装了mapping中的信息 |
CreateIndexRequest | 封装mapping和settins,index,type |
RestHighLevelClient | java访问es服务器的客户端 |
IndicesClient | 索引的客户端 |
CreateIndexResponse | 创建完索引后给出响应的对象 |
5.2.1 创建索引
代码如下,重在理解结构,代码不会写,会抄会用即可↓
public class Demo2 { RestHighLevelClient client = ESClient.getClient(); String index = "person"; String type = "man"; @Test public void createIndex() throws IOException { //1. 准备关于索引的settings Settings.Builder settings = Settings.builder() .put("number_of_shards", 3) .put("number_of_replicas", 1); //2. 准备关于索引的结构mappings XContentBuilder mappings = JsonXContent.contentBuilder() .startObject() .startObject("properties") .startObject("name") .field("type","text") .endObject() .startObject("age") .field("type","integer") .endObject() .startObject("birthday") .field("type","date") .field("format","yyyy-MM-dd") .endObject() .endObject() .endObject(); //3. 将settings和mappings封装到一个Request对象 CreateIndexRequest request = new CreateIndexRequest(index) .settings(settings) .mapping(type,mappings); //4. 通过client对象去连接ES并执行创建索引,缺啥补啥 CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT); //5. 输出 System.out.println("resp:" + resp.toString()); //resp:org.elasticsearch.action.admin.indices.create.CreateIndexResponse@c4f729f4 } }
5.2.2 检查索引是否存在
代码如下
@Test public void exists() throws IOException { //1. 准备request对象 GetIndexRequest request = new GetIndexRequest(); request.indices(index); //2. 通过client去操作 boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); //3. 输出 System.out.println(exists);//索引index存在返回true,不存在返回false }
5.2.3 删除索引
代码如下
@Test public void delete() throws IOException { //1. 准备request对象 DeleteIndexRequest request = new DeleteIndexRequest(); request.indices(index); //2. 通过client对象执行 AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT); //3. 获取返回结果 System.out.println(delete.isAcknowledged());//true }
5.3 Java操作文档
5.3.1 添加文档操作
代码如下↓
添加依赖↓
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.3</version> </dependency>配置实体↓
package com.qf.bean; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor public class Person { @JsonIgnore//id要忽略转换为json,通过其他Java代码来搞定id生成 private Integer id; private String name; private Integer age; @JsonFormat(pattern = "yyyy-MM-dd")//转为json时指定格式 private Date birthday; }
public class Demo3 { ObjectMapper mapper = new ObjectMapper();//这里用到jackson所以上面要先添加依赖↑ RestHighLevelClient client = ESClient.getClient(); String index = "person"; String type = "man"; @Test public void createDoc() throws IOException { //1. 准备一个json数据 Person person = new Person(1,"张三",23,new Date()); String json = mapper.writeValueAsString(person); //2. 准备一个request对象(其他Java代码来搞定id生成,即手动指定id↓) IndexRequest request = new IndexRequest(index,type,person.getId().toString()); request.source(json, XContentType.JSON); //3. 通过client对象执行添加 IndexResponse resp = client.index(request, RequestOptions.DEFAULT); //4. 输出返回结果 System.out.println(resp.getResult().toString()); //添加成功返回的第一次是CREATED,再运行一次是更新是UPDATED } }
结果查询要点击索引匹配,才能点击发现按钮来得到查询的具体数据↓
5.3.2 修改文档
代码如下
@Test public void updateDoc() throws IOException { //1. 创建一个Map,指定需要修改的内容 Map<String,Object> doc = new HashMap<String,Object>(); doc.put("name","张大三"); // 如果id放在map中会把id属性设置到_source里面 String docId = "1"; //2. 创建request对象,封装数据 UpdateRequest request = new UpdateRequest(index,type,docId); request.doc(doc); //3. 通过client对象执行 UpdateResponse update = client.update(request, RequestOptions.DEFAULT); //4. 输出返回结果 System.out.println(update.getResult().toString());//UPDATED }
修改后点击浏览器刷新按钮,数据对比,发现名字更新为张大三了↓
5.3.3 删除文档
代码如下
@Test public void deleteDoc() throws IOException { //1. 封装Request对象 DeleteRequest request = new DeleteRequest(index,type,"1"); //2. client执行 DeleteResponse resp = client.delete(request, RequestOptions.DEFAULT); //3. 输出结果 System.out.println(resp.getResult().toString()); //删除成功返回DELETED,再删没有数据返回NOT_FOUND }
删除后点击浏览器刷新按钮,数据对比,发现确实删除了名字为张大三了这条数据,没有数据了↓
5.4 Java批量操作文档
5.4.1 批量添加
代码如下
@Test public void bulkCreateDoc() throws IOException { //1. 准备多个json数据 Person p1 = new Person(1,"张三",23,new Date()); Person p2 = new Person(2,"李四",24,new Date()); Person p3 = new Person(3,"王五",25,new Date()); String json1 = mapper.writeValueAsString(p1); String json2 = mapper.writeValueAsString(p2); String json3 = mapper.writeValueAsString(p3); //2. 创建Request,将准备好的数据封装进去 BulkRequest request = new BulkRequest(); request.add(new IndexRequest(index,type,p1.getId().toString()).source(json1,XContentType.JSON)); request.add(new IndexRequest(index,type,p2.getId().toString()).source(json2,XContentType.JSON)); request.add(new IndexRequest(index,type,p3.getId().toString()).source(json3,XContentType.JSON)); //3. 用client执行 BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT); //4. 输出结果 System.out.println(resp.toString());//org.elasticsearch.action.bulk.BulkResponse@3aa078fd }
图形化界面es客户端kb查询,点击浏览器刷新按钮,发现数据结果如下,增加了三条数据↓
5.4.2 批量删除
代码如下
@Test public void bulkDeleteDoc() throws IOException { //1. 封装Request对象 BulkRequest request = new BulkRequest(); request.add(new DeleteRequest(index,type,"1")); request.add(new DeleteRequest(index,type,"2")); request.add(new DeleteRequest(index,type,"3")); //2. client执行 BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT); //3. 输出 System.out.println(resp);//org.elasticsearch.action.bulk.BulkResponse@196a42c3 }
图形化界面es客户端kb查询,点击浏览器刷新按钮,发现数据结果如下,删除了三条数据,数据没了↓
5.5 ElasticSearch练习,课下操作,这里只是为了方便等下讲的查询数据用↓
创建索引,指定数据结构
索引名:sms-logs-index
类型名:sms-logs-type
结构如下:
索引结构图 |
---|
5.6 准备数据
导入fastjson依赖,因为下面的Java代码要用到↓
<!-- 先导入fastJSON,做对象序列化为json字符串等操作 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
编写实现类SmsLogs↓
package com.qf.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor public class SmsLogs { private String id;// 唯一ID 1 private Date createDate;// 创建时间 private Date sendDate; // 发送时间 private String longCode;// 发送的长号码 private String mobile;// 下发手机号 private String corpName;// 发送公司名称 private String smsContent; // 下发短信内容 private Integer state; // 短信下发状态 0 成功 1 失败 private Integer operatorId; // '运营商编号 1 移动 2 联通 3 电信 private String province;// 省份 private String ipAddr; //下发服务器IP地址 private Integer replyTotal; //短信状态报告返回时长(秒) private Integer fee; // 费用 }
编写测试类Demo4,提供两个方法,分别用来创建索引库,和增加数据↓
package com.qf.test; import com.alibaba.fastjson.JSON; import com.qf.bean.SmsLogs; import com.qf.utils.ESClient; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.junit.Test; import java.io.IOException; import java.util.Date; public class Demo4 { RestHighLevelClient client = ESClient.getClient(); String index = "sms-logs-index"; String type = "sms-logs-type"; @Test public void createSmsLogsIndex() throws IOException { //1. settings Settings.Builder settings = Settings.builder() .put("number_of_shards", 3) .put("number_of_replicas", 1); //2. mapping. XContentBuilder mapping = JsonXContent.contentBuilder() .startObject() .startObject("properties") .startObject("createDate") .field("type", "date") .endObject() .startObject("sendDate") .field("type", "date") .endObject() .startObject("longCode") .field("type", "keyword") .endObject() .startObject("mobile") .field("type", "keyword") .endObject() .startObject("corpName") .field("type", "keyword") .endObject() .startObject("smsContent") .field("type", "text") .field("analyzer", "ik_max_word") .endObject() .startObject("state") .field("type", "integer") .endObject() .startObject("operatorId") .field("type", "integer") .endObject() .startObject("province") .field("type", "keyword") .endObject() .startObject("ipAddr") .field("type", "ip") .endObject() .startObject("replyTotal") .field("type", "integer") .endObject() .startObject("fee") .field("type", "long") .endObject() .endObject() .endObject(); //3. 添加索引. CreateIndexRequest request = new CreateIndexRequest(index); request.settings(settings); request.mapping(type,mapping); client.indices().create(request, RequestOptions.DEFAULT); System.out.println("OK!!"); } @Test public void addTestData() throws IOException { BulkRequest request = new BulkRequest(); SmsLogs smsLogs = new SmsLogs(); smsLogs.setMobile("13800000000"); smsLogs.setCorpName("途虎养车"); smsLogs.setCreateDate(new Date()); smsLogs.setSendDate(new Date()); smsLogs.setIpAddr("10.126.2.9"); smsLogs.setLongCode("10690000988"); smsLogs.setReplyTotal(10); smsLogs.setState(0); smsLogs.setSmsContent("【途虎养车】亲爱的张三先生/女士,您在途虎购买的货品(单号TH123456)已 到指定安装店多日," + "现需与您确认订单的安装情况,请点击链接按实际情况选择(此链接有效期为72H)。您也可以登录途 虎APP进入" + "“我的-待安装订单”进行预约安装。若您在服务过程中有任何疑问,请致电400-111-8868向途虎咨 询。"); smsLogs.setProvince("北京"); smsLogs.setOperatorId(1); smsLogs.setFee(3); request.add(new IndexRequest(index, type, "21").source(JSON.toJSONString(smsLogs), XContentType.JSON)); smsLogs.setMobile("13700000001"); smsLogs.setProvince("上海"); smsLogs.setSmsContent("【途虎养车】亲爱的刘红先生/女士,您在途虎购买的货品(单号TH1234526)已 到指定安装店多日," + "现需与您确认订单的安装情况,请点击链接按实际情况选择(此链接有效期为72H)。您也可以登录途 虎APP进入" + "“我的-待安装订单”进行预约安装。若您在服务过程中有任何疑问,请致电400-111-8868向途虎咨 询。"); request.add(new IndexRequest(index, type, "22").source(JSON.toJSONString(smsLogs), XContentType.JSON)); // ------------------------------------------------------------------------------------------------------------------- SmsLogs smsLogs1 = new SmsLogs(); smsLogs1.setMobile("13100000000"); smsLogs1.setCorpName("盒马鲜生"); smsLogs1.setCreateDate(new Date()); smsLogs1.setSendDate(new Date()); smsLogs1.setIpAddr("10.126.2.9"); smsLogs1.setLongCode("10660000988"); smsLogs1.setReplyTotal(15); smsLogs1.setState(0); smsLogs1.setSmsContent("【盒马】您尾号12345678的订单已开始配送,请在您指定的时间收货不要走开 哦~配送员:" + "刘三,电话:13800000000"); smsLogs1.setProvince("北京"); smsLogs1.setOperatorId(2); smsLogs1.setFee(5); request.add(new IndexRequest(index, type, "23").source(JSON.toJSONString(smsLogs1), XContentType.JSON)); smsLogs1.setMobile("18600000001"); smsLogs1.setProvince("上海"); smsLogs1.setSmsContent("【盒马】您尾号7775678的订单已开始配送,请在您指定的时间收货不要走开 哦~配送员:" + "王五,电话:13800000001"); request.add(new IndexRequest(index, type, "24").source(JSON.toJSONString(smsLogs1), XContentType.JSON)); // ------------------------------------------------------------------------------------------------------------------- SmsLogs smsLogs2 = new SmsLogs(); smsLogs2.setMobile("15300000000"); smsLogs2.setCorpName("滴滴打车"); smsLogs2.setCreateDate(new Date()); smsLogs2.setSendDate(new Date()); smsLogs2.setIpAddr("10.126.2.8"); smsLogs2.setLongCode("10660000988"); smsLogs2.setReplyTotal(50); smsLogs2.setState(1); smsLogs2.setSmsContent("【滴滴单车平台】专属限时福利!青桔/小蓝月卡立享5折,特惠畅骑30天。" + "戳 https://xxxxxx退订TD"); smsLogs2.setProvince("上海"); smsLogs2.setOperatorId(3); smsLogs2.setFee(7); request.add(new IndexRequest(index, type, "25").source(JSON.toJSONString(smsLogs2), XContentType.JSON)); smsLogs2.setMobile("18000000001"); smsLogs2.setProvince("武汉"); smsLogs2.setSmsContent("【滴滴单车平台】专属限时福利!青桔/小蓝月卡立享5折,特惠畅骑30天。" + "戳 https://xxxxxx退订TD"); request.add(new IndexRequest(index, type, "26").source(JSON.toJSONString(smsLogs2), XContentType.JSON)); // ------------------------------------------------------------------------------------------------------------------- SmsLogs smsLogs3 = new SmsLogs(); smsLogs3.setMobile("13900000000"); smsLogs3.setCorpName("招商银行"); smsLogs3.setCreateDate(new Date()); smsLogs3.setSendDate(new Date()); smsLogs3.setIpAddr("10.126.2.8"); smsLogs3.setLongCode("10690000988"); smsLogs3.setReplyTotal(50); smsLogs3.setState(0); smsLogs3.setSmsContent("【招商银行】尊贵的李四先生,恭喜您获得华为P30 Pro抽奖资格,还可领100 元打" + "车红包,仅限1天"); smsLogs3.setProvince("上海"); smsLogs3.setOperatorId(1); smsLogs3.setFee(8); request.add(new IndexRequest(index, type, "27").source(JSON.toJSONString(smsLogs3), XContentType.JSON)); smsLogs3.setMobile("13990000001"); smsLogs3.setProvince("武汉"); smsLogs3.setSmsContent("【招商银行】尊贵的李四先生,恭喜您获得华为P30 Pro抽奖资格,还可领100 元打" + "车红包,仅限1天"); request.add(new IndexRequest(index, type, "28").source(JSON.toJSONString(smsLogs3), XContentType.JSON)); // ------------------------------------------------------------------------------------------------------------------- SmsLogs smsLogs4 = new SmsLogs(); smsLogs4.setMobile("13700000000"); smsLogs4.setCorpName("中国平安保险有限公司"); smsLogs4.setCreateDate(new Date()); smsLogs4.setSendDate(new Date()); smsLogs4.setIpAddr("10.126.2.8"); smsLogs4.setLongCode("10690000998"); smsLogs4.setReplyTotal(18); smsLogs4.setState(0); smsLogs4.setSmsContent("【中国平安】奋斗的时代,更需要健康的身体。中国平安为您提供多重健康保 障,在奋斗之路上为您保驾护航。退订请回复TD"); smsLogs4.setProvince("武汉"); smsLogs4.setOperatorId(1); smsLogs4.setFee(5); request.add(new IndexRequest(index, type, "29").source(JSON.toJSONString(smsLogs4), XContentType.JSON)); smsLogs4.setMobile("13990000002"); smsLogs4.setProvince("武汉"); smsLogs4.setSmsContent("【招商银行】尊贵的王五先生,恭喜您获得iphone 56抽奖资格,还可领5 元打" + "车红包,仅限100天"); request.add(new IndexRequest(index, type, "30").source(JSON.toJSONString(smsLogs4), XContentType.JSON)); // ------------------------------------------------------------------------------------------------------------------- SmsLogs smsLogs5 = new SmsLogs(); smsLogs5.setMobile("13600000000"); smsLogs5.setCorpName("中国移动"); smsLogs5.setCreateDate(new Date()); smsLogs5.setSendDate(new Date()); smsLogs5.setIpAddr("10.126.2.8"); smsLogs5.setLongCode("10650000998"); smsLogs5.setReplyTotal(60); smsLogs5.setState(0); smsLogs5.setSmsContent("【北京移动】尊敬的客户137****0000,5月话费账单已送达您的139邮箱," + "点击查看账单详情 http://y.10086.cn/; " + " 回Q关闭通知,关注“中国移动139邮箱”微信随时查账单【中国移动 139邮箱】"); smsLogs5.setProvince("武汉"); smsLogs5.setOperatorId(1); smsLogs5.setFee(4); request.add(new IndexRequest(index, type, "31").source(JSON.toJSONString(smsLogs5), XContentType.JSON)); smsLogs5.setMobile("13990001234"); smsLogs5.setProvince("山西"); smsLogs5.setSmsContent("【北京移动】尊敬的客户137****1234,8月话费账单已送达您的126邮箱,\" + \"点击查看账单详情 http://y.10086.cn/; \" + \" 回Q关闭通知,关注“中国移动126邮箱”微信随时查账单【中国移动 126邮箱】"); request.add(new IndexRequest(index, type, "32").source(JSON.toJSONString(smsLogs5), XContentType.JSON)); // ------------------------------------------------------------------------------------------------------------------- client.bulk(request, RequestOptions.DEFAULT); System.out.println("OK!"); } }
最后点击索引匹配,然后来到发现按钮页面可以查看到添加了很多数据,方便下面用es做各种查询用↓
六、 ElasticSearch的各种查询
6.1 term&terms查询【重点
】
6.1.1 term查询(分页)
term的查询是代表完全匹配,搜索之前不会对你搜索的关键字进行分词,对你的关键字去文档分词库中去匹配内容。
# term查询 POST /sms-logs-index/sms-logs-type/_search { "from": 0, "size": 5, "query": { "term": { "province": { "value": "北京" } } } }
max_score匹配度越高,数据的排名就越靠前↑
代码实现方式
// Java代码实现方式 @Test public void termQuery() throws IOException { //1. 创建Request对象 SearchRequest request = new SearchRequest(index); request.types(type); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.from(0); builder.size(5); builder.query(QueryBuilders.termQuery("province","北京")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 获取到_source中的数据,并展示 for (SearchHit hit : resp.getHits().getHits()) { Map<String, Object> result = hit.getSourceAsMap(); System.out.println(result); } }
上面的Java代码的resp.getHits().getHits(),分别对应查询结果下图中的两个hits命中数据↓
6.1.2 terms查询
terms和term的查询机制是一样的,都不会将指定的查询关键字进行分词,直接去分词库中匹配,找到相应文档内容。
terms是在针对一个字段包含多个值的时候使用。
term:where province = 北京;
terms:where province = 北京 or province = ?or province = ?
# terms查询 POST /sms-logs-index/sms-logs-type/_search { "query": { "terms": { "province": [ "北京", "山西", "武汉" ] } } } ## 返回指定的列 POST /sms-logs-index/sms-logs-type/_search { "_source": ["province","fee"], "query": { "terms": { "province": [ "北京", "山西", "武汉" ] } } }
代码实现方式
// Java实现 @Test public void termsQuery() throws IOException { //1. 创建request SearchRequest request = new SearchRequest(index); request.types(type); //2. 封装查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.query(QueryBuilders.termsQuery("province","北京","山西")); request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出_source for (SearchHit hit : resp.getHits().getHits()) { System.out.println(hit.getSourceAsMap()); } }
6.2 match查询【重点
】
match查询属于高层查询,他会根据你查询的字段类型不一样,采用不同的查询方式。
查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
如果查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
如果查询的内容时一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。
match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起而已。
6.2.1 match_all查询
查询全部内容,不指定任何查询条件。
# match_all查询 POST /sms-logs-index/sms-logs-type/_search { "query": { "match_all": {} } }
代码实现方式
// java代码实现 @Test public void matchAllQuery() throws IOException { //1. 创建Request SearchRequest request = new SearchRequest(index); request.types(type); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.query(QueryBuilders.matchAllQuery()); builder.size(20); // ES默认只查询10条数据,如果想查询更多,添加size request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits().getHits()) { System.out.println(hit.getSourceAsMap()); } System.out.println(resp.getHits().getHits().length); }
6.2.2 match查询
指定一个Field作为筛选的条件
# match查询 POST /sms-logs-index/sms-logs-type/_search { "query": { "match": { "smsContent": "收货安装" } } }
代码实现方式
@Test public void matchQuery() throws IOException { //1. 创建Request SearchRequest request = new SearchRequest(index); request.types(type); //2. 指定查询条件 SearchSourceBuilder builder = new SearchSourceBuilder(); //----------------------------------------------- builder.query(QueryBuilders.matchQuery("smsContent","收货安装")); //----------------------------------------------- request.source(builder); //3. 执行查询 SearchResponse resp = client.search(request, RequestOptions.DEFAULT); //4. 输出结果 for (SearchHit hit : resp.getHits(