技术标签: elasticsearch lucene 服务框架高级 搜索引擎
ElasticSearch
elasticsearch
是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
例如:
GitHub
搜索代码 elasticsearch
结合kibana
、Logstash
、Beats
,就是elastic stack
(ELK
)。
被广泛应用在日志数据分析、实时监控等领域:
而elasticsearch
是elastic stack
的核心,负责存储、搜索、分析数据。
elasticsearch
底层是基于lucene来实现的。
Lucene是一个Java语言的搜索引擎类库(jar包),是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:https://lucene.apache.org/
。
elasticsearch的发展历史:
Elasticsearch
。目前比较知名的搜索引擎技术排名:
虽然在早期,Apache Solr是最主要的搜索引擎技术,但随着发展elasticsearch已经渐渐超越了Solr,独占鳌头:
什么是elasticsearch?
什么是elastic stack(ELK)?
elasticsearch
为核心的技术栈,包括beats、Logstash、kibana、elasticsearch什么是Lucene?
倒排索引的概念是基于MySQL
这样的正向索引而言的。
那么什么是正向索引呢?例如给下表(tb_goods
)中的id创建索引:
如果是根据id查询,那么直接走索引,查询速度非常快。
但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下:
1)用户搜索数据,条件是title符合"%手机%"
2)逐行获取数据,比如id为1的数据
3)判断数据中的title是否符合用户搜索条件
4)如果符合则放入结果集,不符合则丢弃。回到步骤1
逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。
倒排索引中有两个非常重要的概念:
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息Term
):文档按照语义分成的词语(对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条)。例如:我是上国人,就可以分为:我、是、上国人、上国、上人这样的几个词条创建倒排索引是对正向索引的一种特殊处理,流程如下:
如图:
倒排索引的搜索流程如下(以搜索"华为手机"为例):
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入内容分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。
4)拿着文档id到正向索引中查找具体文档。
如图:
虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
那么为什么一个叫做正向索引,一个叫做倒排索引呢?
是不是恰好反过来了?
那么两者方式的优缺点是什么呢?
正向索引:
倒排索引:
elasticsearch
中有很多独有的概念,与mysql
中略有差别,但也有相似之处。
elasticsearch
是面向 文档(Document) 存储的,一条数据就是一个文档,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
而Json文档中往往包含很多的字段(Field),类似于数据库中的列,Json文档中的字段。
索引(Index),就是 相同类型的文档的集合。
例如:
因此,我们可以把索引当做是数据库中的表。
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,比如字段名称,类型,类似表的结构约束。
我们统一的把mysql
与elasticsearch
的概念做一下对比:
MySQL | Elasticsearch | 说明 |
---|---|---|
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
是不是说,我们学习了elasticsearch就不再需要mysql了呢?
并不是如此,两者各自有自己的擅长支出:
Mysql
:擅长事务类型操作,可以确保数据的安全和一致性Elasticsearch
:擅长海量数据的搜索、分析、计算因此在企业中,往往是两者结合使用:
es
、kibana
参考课前资料:
1.部署单点es——2.部署kibana
es
在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。
我们在kibana
的DevTools
中测试:
处理中文分词,一般会使用IK分词器。https://github.com/medcl/elasticsearch-analysis-ik
安装IK分词器,参考课前资料《安装elasticsearch.md》:
3.安装IK分词器(适用于中文分词)
参考课前资料:
ik分词器包含两种模式(根据需求去做选择):
ik_smart
:最少切分,粗粒度ik_max_word
:最细切分,细粒度随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给”,“传智播客” 等。
所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。
1)打开IK分词器config目录:
2)在IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
3)新建一个 ext.dic
(与IKAnalyzer.cfg.xml在同一目录下),可以参考config
目录下复制一个配置文件进行修改
传智播客
奥力给
stopword.dic
文件本身就有
4)重启elasticsearch
docker restart es
# 查看 日志
docker logs -f elasticsearch
日志中已经成功加载ext.dic
配置文件
5)测试效果:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "传智播客Java就业超过90%,奥力给!"
}
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。
1)IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
3)在stopword.dic
添加停用词
张大大
4)重启elasticsearch
# 重启服务
docker restart elasticsearch
docker restart kibana
# 查看 日志
docker logs -f elasticsearch
日志中已经成功加载stopword.dic
配置文件
5)测试效果:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "传智播客Java就业率超过95%,张大大都点赞,奥力给!"
}
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
分词器的作用是什么?
IK分词器有几种模式?
ik_smart
:智能切分,粗粒度ik_max_word
:最细切分,细粒度IK
分词器如何拓展词条?如何停用词条?
config
目录的IkAnalyzer.cfg.xml
文件添加拓展词典和停用词典索引库就类似数据库表,mapping
映射就类似表的结构。
我们要向es中存储数据,必须先创建“库”和“表”。
mapping
是对索引库中文档的约束,常见的mapping
属性包括:
(没有数组类型,但允许某个元素有多个值)
type
:字段数据类型,常见的简单类型有:
text
(可分词的文本)、keyword
(精确值(不可分词),例如:品牌、国家、ip地址)index
:是否创建索引,默认为trueanalyzer
:使用哪种分词器properties
:该字段的子字段(如下面name下的firstName)例如下面的json
文档:
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "黑马程序员Java讲师",
"email": "[email protected]",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}
对应的每个字段映射(mapping
):
这里我们统一使用Kibana
编写DSL
的方式来演示。
格式:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
根据:
#创建索引库
PUT /heima
{
"mappings": {
#做映射的
"properties": {
#代表里面是一个一个的字段了
"info":{
# 字段名1
"type": "text", #字段数据类型
"analyzer": "ik_smart" #分词器
},
"email":{
# 字段名2
"type": "keyword",
"index": "false" #是否创建爱你索引,默认为true;
},
"name":{
# 字段名3(嵌套类型的)
"type":"object",
"properties": {
"firstName": {
"type": "keyword"
},
"lastName": {
"type": "keyword"
}
}
},
// ... 略
}
}
}
运行结果:
基本语法:
格式:
GET /索引库名
示例:
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库 一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法说明:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
示例:
语法:
格式:
DELETE /索引库名
在kibana
中测试:
这时就查不到了
索引库操作有哪些?
PUT /索引库名
GET /索引库名
PUT /索引库名/_mapping
DELETE /索引库名
语法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
示例:
POST /heima/_doc/1
{
"info": "程序员",
"email": "[email protected]",
"name": {
"firstName": "云",
"lastName": "赵"
}
}
响应:
根据rest
风格,新增是post
,查询应该是get
,不过查询一般都需要条件,这里我们把文档id
带上。
语法:
GET /{
索引库名称}/_doc/{
id}
通过kibana查看数据:
GET /heima/_doc/1
查看结果:
删除使用DELETE
请求,同样,需要根据id
进行删除:
语法:
DELETE /{
索引库名}/_doc/id值
示例:
# 根据id删除数据
DELETE /heima/_doc/1
结果:
修改有两种方式:
全量修改是覆盖原来的文档,其本质是:
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
PUT /{
索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
示例:
PUT /heima/_doc/1
{
"info": "黑马程序员高级Java讲师",
"email": "[email protected]",
"name": {
"firstName": "云",
"lastName": "赵"
}
}
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
POST /{
索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
示例:
POST /heima/_update/1
{
"doc": {
"email": "[email protected]"
}
}
文档操作有哪些?
POST /{索引库名}/_doc/文档id { json文档 }
GET /{索引库名}/_doc/文档id
DELETE /{索引库名}/_doc/文档id
PUT /{索引库名}/_doc/文档id { json文档 }
POST /{索引库名}/_update/文档id { "doc": {字段}}
我们最终的目的是通过Java代码操作es;
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
其中的Java Rest Client
又包括两种:
我们学习的是Java HighLevel Rest Client客户端API
首先导入课前资料提供的数据库数据:
数据结构如下:
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
`address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
`price` int(10) NOT NULL COMMENT '酒店价格;例:329',
`score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
`brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
`city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
`star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
`business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
`latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
`longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
导入数据:
-- ----------------------------
-- Records of tb_hotel
-- ----------------------------
INSERT INTO `tb_hotel` VALUES (36934, '7天连锁酒店(上海宝山路地铁站店)', '静安交通路40号', 336, 37, '7天酒店', '上海', '二钻', '四川北路商业区', '31.251433', '121.47522', 'https://m.tuniucdn.com/fb2/t1/G1/M00/3E/40/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (38609, '速8酒店(上海赤峰路店)', '广灵二路126号', 249, 35, '速8', '上海', '二钻', '四川北路商业区', '31.282444', '121.479385', 'https://m.tuniucdn.com/fb2/t1/G2/M00/DF/96/Cii-TFkx0ImIQZeiAAITil0LM7cAALCYwKXHQ4AAhOi377_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (38665, '速8酒店上海中山北路兰田路店', '兰田路38号', 226, 35, '速8', '上海', '二钻', '长风公园地区', '31.244288', '121.422419', 'https://m.tuniucdn.com/fb2/t1/G2/M00/EF/86/Cii-Tlk2mV2IMZ-_AAEucgG3dx4AALaawEjiycAAS6K083_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2003479905, '上海榕港万怡酒店', '新松江路1277号', 798, 46, '万怡', '上海', '四钻', '佘山/松江大学城', '31.038198', '121.210178', 'https://m.tuniucdn.com/fb3/s1/2n9c/2GM761BYH8k15qkNrJrja3cwfr2D_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2009548883, '和颐至尚酒店(北京首都机场新国展店)', '府前二街6号', 611, 46, '和颐', '北京', '三钻', '首都机场/新国展地区', '40.063953', '116.576829', 'https://m.tuniucdn.com/fb3/s1/2n9c/43zCTomkMSkUfZByZxn77YH2XidJ_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2011785622, '北京世园凯悦酒店', '阜康南路1号院1号楼A', 558, 47, '凯悦', '北京', '五星级', '延庆休闲度假区', '40.440732', '115.963259', 'https://m.tuniucdn.com/fb3/s1/2n9c/uhGcQze3zZQxe4avSU8BysgYVvx_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2022598930, '上海宝华喜来登酒店', '南奉公路3111弄228号', 2899, 46, '喜来登', '上海', '五钻', '奉贤开发区', '30.921659', '121.575572', 'https://m.tuniucdn.com/fb2/t1/G6/M00/45/BD/Cii-TF3ZaBmIStrbAASnoOyg7FoAAFpYwEoz9oABKe4992_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2048050570, '汉庭酒店(深圳坪山火车站店)', '新和路127-2号', 436, 47, '汉庭', '深圳', '二钻', '坪山高铁站商圈', '22.700753', '114.339089', 'https://m.tuniucdn.com/fb3/s1/2n9c/2nXN2bWjfoqoTkPwHvLJQPYz17qD_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2056132395, '深圳深铁皇冠假日酒店', '深南大道9819号', 340, 47, '皇冠假日', '深圳', '五钻', '科技园', '22.538923', '113.944794', 'https://m.tuniucdn.com/fb3/s1/2n9c/eBLtrED2uJs7yURWfjnWge9dT1P_w200_h200_c1_t0.jpg');
INSERT INTO `tb_hotel` VALUES (2062643512, '深圳国际会展中心希尔顿酒店', '展丰路80号', 285, 46, '希尔顿', '深圳', '五钻', '深圳国际会展中心商圈', '22.705335', '113.77794', 'https://m.tuniucdn.com/fb3/s1/2n9c/2SHUVXNrN5NsXsTUwcd1yaHKbrGq_w200_h200_c1_t0.jpg');
SET FOREIGN_KEY_CHECKS = 1;
然后导入课前资料提供的项目:
项目结构如图(配置文件要改成自己的信息):
创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
其中:
ik_max_word
数据表结构
来看下酒店数据的索引库结构:
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false #不创建索引;
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
几个特殊字段说明:
location
:地理坐标,里面包含精度、纬度all
:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索地理坐标说明:
copy_to
说明:
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
分为三步:
1)引入es
的RestHighLevelClient
依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.1</version>
</dependency>
2)因为SpringBoot
默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:
<properties>
<java.version>1.8</java.version>
<!--这一句-->
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient
:
初始化的代码如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
这里为了单元测试方便,我们创建一个测试类HotelIndexTest
,然后将初始化的代码编写在@BeforeEach
方法中:
package cn.itcast.hotel;
public class HotelIndexTest {
private RestHighLevelClient client; //将 RestHighLevelClient 定位成员变量;下面可以复用;
@BeforeEach
//客户端初始化创建
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
//客户端销毁
void tearDown() throws IOException {
this.client.close();
}
}
创建索引库的API如下:
代码分为三步:
在hotel-demo的cn.itcast.hotel.constants包下,创建一个类,定义mapping映射的JSON字符串常量:
package cn.itcast.hotel.constants;
public class HotelConstants {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"address\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"price\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"score\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"brand\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"city\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"starName\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"location\":{\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"pic\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"all\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
在hotel-demo
中的HotelIndexTest
测试类中,编写单元测试,实现创建索引:
@Test
void createHotelIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
删除索引库的DSL语句非常简单:
DELETE /hotel
与创建索引库相比:
所以代码的差异,注意体现在Request对象上。依然是三步走:
DeleteIndexRequest
对象delete
方法在hotel-demo
中的HotelIndexTest
测试类中,编写单元测试,实现删除索引:
@Test
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
判断索引库是否存在,本质就是查询,对应的DSL是:
GET /hotel
因此与删除的Java代码流程是类似的。依然是三步走:
Request
对象。这次是GetIndexRequest
对象exists
方法@Test
void testExistsHotelIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}
JavaRestClient
操作elasticsearch
的流程基本类似。核心是client.indices()
方法来获取索引库的操作对象。
索引库操作的基本步骤:
RestHighLevelClient
XxxIndexRequest
。XXX
是Create
、Get
、Delete
DSL
( Create时需要,其它是无参)RestHighLevelClient#indices().xxx()
方法,xxx是create、exists、delete为了与索引库操作分离,我们再次参加一个测试类,做两件事情:
RestHighLevelClient
IHotelService
去查询,所以注入这个接口package cn.itcast.hotel;
@SpringBootTest
public class HotelDocumentTest {
@Autowired
private IHotelService hotelService; //利用对象完成酒店数据的查询;
private RestHighLevelClient client;
@BeforeEach
//客户端初始化创建
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
//客户端销毁
void tearDown() throws IOException {
this.client.close();
}
}
我们要将数据库的酒店数据查询出来,写入elasticsearch
中。
数据库查询后的结果是一个Hotel类型的对象。结构如下:
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String longitude;
private String latitude;
private String pic;
}
与我们的索引库结构存在差异:
longitude
和latitude
需要合并为location
因此,我们需要定义一个新的类型,与索引库结构吻合:
实现数据库数据到索引库数据的类型转换;
package cn.itcast.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
新增文档的DSL语句如下:
POST /{
索引库名}/_doc/1
{
"name": "Jack",
"age": 21
}
对应的java代码如图:
可以看到与创建索引库类似,同样是三步走:
变化的地方在于,这里直接使用client.xxx()
的API,不再需要client.indices()
了。
我们导入酒店数据,基本流程一致,但是需要考虑几点变化:
因此,代码整体步骤如下:
在hotel-demo
的HotelDocumentTest
测试类中,编写单元测试:
@Test
void testAddDocument() throws IOException {
// 1.根据id查询酒店数据
Hotel hotel = hotelService.getById(61083L);
// 2.转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel); //从数据库类型转换成索引库所需要的类型;
// 3.将HotelDoc转json
String json = JSON.toJSONString(hotelDoc); //json的序列化,把对象变成json的风格;
// 1.准备Request对象
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2.准备Json文档
request.source(json, XContentType.JSON);
// 3.发送请求
client.index(request, RequestOptions.DEFAULT);
}
查询的DSL语句如下:
GET /hotel/_doc/{
id}
非常简单,因此代码大概分两步:
不过查询的目的是得到结果,解析为HotelDoc
,因此难点是结果的解析。完整代码如下:
可以看到,结果是一个JSON,其中文档放在一个_source
属性中,因此解析就是拿到_source
,反序列化为Java对象即可。
与之前类似,也是三步走:
在hotel-demo
的HotelDocumentTest
测试类中,编写单元测试:
@Test
void testGetDocumentById() throws IOException {
// 1.准备Request
GetRequest request = new GetRequest("hotel", "61082");
// 2.发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class); //将json反序列化;
System.out.println(hotelDoc);
}
删除的DSL为是这样的:
DELETE /hotel/_doc/{
id}
与查询相比,仅仅是请求方式从DELETE
变成GET
,可以想象Java
代码应该依然是三步走:
Request
对象,因为是删除,这次是DeleteRequest
对象。要指定索引库名和id
client.delete()
方法在hotel-demo
的HotelDocumentTest
测试类中,编写单元测试:
@Test
void testDeleteDocument() throws IOException {
// 1.准备Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
修改我们讲过两种方式:
在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:
这里不再赘述,我们主要关注增量修改。
代码示例如图:
与之前类似,也是三步走:
Request
对象。这次是修改,所以是UpdateRequest
JSON
文档,里面包含要修改的字段client.update()
方法在hotel-demo
的HotelDocumentTest
测试类中,编写单元测试:
@Test
void testUpdateDocument() throws IOException {
// 1.准备Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2.准备请求参数
request.doc(
"price", "952",
"starName", "四钻"
);
// 3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
案例需求:利用BulkRequest
批量将数据库数据导入到索引库中。
步骤如下:
批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送。
其中提供了一个add方法,用来添加其他请求:
可以看到,能添加的请求包括:
因此Bulk
中添加了多个IndexRequest
,就是批量新增功能了。示例:
其实还是三步走:
我们在导入酒店数据时,将上述代码改造成for循环处理即可。
在hotel-demo
的HotelDocumentTest
测试类中,编写单元测试:
@Test
void testBulkRequest() throws IOException {
// 批量查询酒店数据
List<Hotel> hotels = hotelService.list(); //查询所有酒店数据;
// 1.创建Request
BulkRequest request = new BulkRequest();
// 2.准备参数,添加多个新增的Request;(文档的转换与请求的创建)
for (Hotel hotel : hotels) {
// 2.1.转换为文档类型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2.2.创建新增文档的Request对象
request.add(new IndexRequest("hotel") //指定索引库名
.id(hotelDoc.getId().toString()) //指定id;
.source(JSON.toJSONString(hotelDoc), XContentType.JSON)); //指定json
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
文档操作的基本步骤:
文章浏览阅读5.3k次。定义语法:public static URI create(String str)用例:URI uri = URI.create("http://java2s.com");_java.net.uri 转string
文章浏览阅读1.4k次。目录LNMP环境安装安装和配置 git安装 Composer安装 redisLaravel 项目安装拉取 github 上的 Laravel 项目代码安装依赖配置环境变量数据配置权限配置Nginx 配置elasticsearch0.安装JDK111.下载Elasticsearch72.下载完了解压压缩包3.移动目录4.新建一..._centos 7 laravel框架的.env
文章浏览阅读517次。北京时间2020年10月14日凌晨,苹果第二次秋季发布会成功落幕,会上发布了旗下搭载最新 iOS14 系统的 iPhone 12 系列智能手机和最新一代 HomePod mini 智能音箱,为了环保理念,苹果在此次发布会之后,官方商店在售 iPhone 系列所有产品均已不再标配 EarPods 有线耳机。因此购买了 iPhone 12 的小伙伴就需要自行配置耳机,而TWS真无线耳机凭借着..._android 耳机录音
文章浏览阅读2.7k次。题目地址:1003. 我要通过!(20)1. 字符串中必须仅有P, A, T这三种字符,不可以包含其它字符;2. 任意形如 xPATx 的字符串都可以获得“答案正确”,其中 x 或者是空字符串,或者是仅由字母 A 组成的字符串;3. 如果 aPbTc 是正确的,那么 aPbATca 也是正确的,其中 a, b, c 均或者是空字符串,或者是仅由字母 A 组成的字符串。_java实现1003
文章浏览阅读7.3k次。今天在公司问老大,公司的项目底层,是使用的TCP,因为可靠,自动断线重连,在底层都实现了,但是我记得TCP也会有掉包的问题,所以这文章就诞生了——关于TCP掉包的问题,TCP是基于不可靠的网络实现可靠的传输,肯定也会存在掉包的情况。 如果通信中发现缺少数据或者丢包,那么,最大的可能在于程序发送的过程或者接收的过程出现问题。 例如服务器给客户端发大量数据,Send的频率很高,_tcp的客户端发送报文给服务器,不产生丢包或网络阻塞,但是数据不一致是因为
文章浏览阅读193次。视频地址学徒卡夫 - 卡夫的Mac 03 - Mac下使用Docker配置PHP环境https://www.bilibili.com/vide...安装 Docker下载镜像# 安装基础镜像 - ubuntu:14.04# 拉取镜像(拉取镜像经常会失败,尝试几次就好了)docker pull ubuntu:14.04# 设置以守护进程在后台运行docker run -dit --name my-l..._mac laravel php7docker环境镜像
文章浏览阅读4.7k次,点赞9次,收藏44次。文章目录1.获取安装包地址并下载2.解压安装包3../configure配置4.make编译5.make install 安装6.测试1.获取安装包地址并下载下载源代码安装包文件,以 APACHE 服务为例,我们需要进入https://httpd.apache.org/找到httpd的软件包。如果想要在虚拟机中下载,则复制链接地址,然后使用 wget 链接地址 下载2.解压安装包使用 tar xvf 压缩包名字 将压缩包解压,解压后会在同级目录下出现一个以软件包为名字的文件夹,也可 -C 指_linux源代码编译安装
文章浏览阅读3.9w次,点赞8次,收藏29次。方式一: 利用FastJson 把对象转化为Map/** * * @MethodName: getUserInfoDataByUserId * @Description: 根据useId查询用户信息,封装成map key:属性名,value:属性值 * home.php?mod=space&uid=952169 userId 用户userId * @return */ public Map<String, String&_java 撖寡情頧柒ap
文章浏览阅读3.8w次,点赞43次,收藏96次。Vscode的相对路径读取问题及处理差异首先说一下vscode相对路径与一般编辑器的不同符号vscode一般的“./2.txt”相对于工作路径(.vscode文件夹所在的位置)相对于当前运行文件“2.txt”相对于工作路径相对于当前运行文件“/2.txt”相对于磁盘根目录相对于磁盘根目录问题处理案例:工作路径如下,执行文件为 readfi..._解决vscode 相对路径
文章浏览阅读922次。MySQL_alter table zhsq_yw_person drop birth
文章浏览阅读2.9w次,点赞96次,收藏250次。Unix 与 C 语言的关系Unix 确实是用 C 语言编写的,而且是世界上第一个用 C 语言编写的操作系统。但是 Unix 是怎么产生的?C 语言又是怎么产生的?Unix 为什么要用 C 语言来编写?相信看完这篇文章你很快就会有了答案。Multics 和 Unix1964年,在 Unix 诞生之前,美国贝尔实验室、通用电气、和麻省理工学院联合开发了一个项目,叫做 Multics操作系统。Multics 在当时取得了巨大的成功,因为它给了人们一个真正可用并且很稳定的操作系统。当时从学校._操作系统是用什么写的
文章浏览阅读4.4w次。org.apache.cxf.service.factory.ServiceConstructionException: Failed to create service. at org.apache.cxf.wsdl11.WSDLServiceFactory.<init>(WSDLServiceFactory.java:76) at org.apach..._org.apache.cxf.service.factory.serviceconstructionexception: failed to creat