目录
es索引库操作
mapping映射操作
索引库的CURD操作
1.创建索引库和映射
编辑 2.查询索引库
3.删除索引库
4.修改索引库
5.总结
文档的CURD操作
1.新增文档
2.查询文档
3.删除文档
4.修改文档
全量修改
增量修改
5.总结
RestAPI
使用API例子
需要的数据库表结构
分析这个数据表的结构,然后构建索引库和mapping
索引库结构说明:
初始化RestClient
1.导入es的RestHighLevelClient依赖:
2.因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本,换成自己的es版本:
3.初始化RestHighLevelClient,即维护一个bean到IoC容器
使用RestHighLevelClient构建索引库及其mapping
1.构建json数据常量
2.创建索引库
3.测试是否构建成功
使用RestHighLevelClient查询索引库和删除索引库
总结
RestClient操作文档
新增文档
构建索引库实体类
因此,我们需要定义一个新的类型,与索引库结构吻合:
新增文档的DSL语句如下:
代码实现
测试
查询文档
修改文档
删除文档
批量导入文档
es索引库操作
索引库类似于数据库中的表,mapping(映射)相当于表的结构
因此我们要向es存储数据时,必须先创建索引库和mapping(映射)
mapping映射操作
mapping是对索引库中文档的约束,常见的mapping属性有:
1.type:字段数据类型,常见的简单类型有
- 字符串:text(可分词文本),keyword(精确词,不可进行分词)
- 数值:integer,long,short,byte,double,float
- 布尔值:boolean
- 日期:date
- 对象:object
2.index:是否为这个字段创建索引,默认为true
3.analyzer:这个字段使用哪种分词器(前提为type类型的text可分词文本)
4.properties:该字段的子字段
例如下面的json文档:
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "我是最厉害的",
"email": "zy@itcast.cn",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}
对应的每个字段映射(mapping):
- age:类型为 integer;参与搜索,因此需要index为true;无需分词器
- weight:类型为float;参与搜索,因此需要index为true;无需分词器
- isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
- info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
- email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
- score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
- name:类型为object,需要定义多个子属性
- name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
- name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
索引库的CURD操作
这里我们使用Kibana编写DSL的方式来演示。
1.创建索引库和映射
基本语法:
- 请求方式:PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
2.查询索引库
基本语法:
-
请求方式:GET
-
请求路径:/索引库名
-
请求参数:无
GET /person
3.删除索引库
语法:
-
请求方式:DELETE
-
请求路径:/索引库名
-
请求参数:无
DELETE /person
查询不到person索引库
4.修改索引库
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
再次查询,birthday字段已经添加成功
5.总结
索引库操作有哪些?
- 创建索引库:PUT /索引库名
- 查询索引库:GET /索引库名
- 删除索引库:DELETE /索引库名
- 添加字段:PUT /索引库名/_mapping
文档的CURD操作
我们在数据库中创建了一张数据表,和表的结构后,是不是要开始向表中插入数据了。
在索引库中也是这样,我们已经创建了索引库,并设置了mapping(映射),现在我们要向索引库中插入文档数据
1.新增文档
语法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
显示创建成功即,增加成功
2.查询文档
根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。
语法
GET /{索引库名称}/_doc/{id}
例子
#查询索引库的id为1的文档
GET /person/_doc/1
3.删除文档
删除使用DELETE请求,同样,需要根据id进行删除:
语法:
DELETE /{索引库名}/_doc/id值
例子
DELETE /person/_doc/1
4.修改文档
修改有两种方式:
- 全量修改:直接覆盖原来的文档
- 增量修改:修改文档中的部分字段
全量修改
全量修改是覆盖原来的文档,其本质是:
- 先根据指定的id删除文档
- 再新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
增量修改
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
例子:修改姓名
#增量修改
POST /person/_update/1
{
"doc":{
"name":{"lastName":"何"}
}
}
5.总结
文档操作有哪些?
- 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
- 查询文档:GET /{索引库名}/_doc/文档id
- 删除文档:DELETE /{索引库名}/_doc/文档id
- 修改文档:
- 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
- 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}
RestAPI
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:Elasticsearch Clients | Elastic
其中的Java Rest Client又包括两种:
- Java Low Level Rest Client
- Java High Level Rest Client
这里使用的是Java HighLevel Rest Client客户端API
使用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;
分析这个数据表的结构,然后构建索引库和mapping
创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
- 字段名
- 字段数据类型
- 是否参与搜索(是否需要index索引)
- 是否需要分词
- 如果分词,分词器是什么?
其中:
- 字段名、字段数据类型,可以参考数据表结构的名称和类型
- 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
- 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
- 分词器,我们可以统一使用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"
}
}
}
}
索引库结构说明:
这里将name,brand,city字段都使用了mapping的copy_to属性复制到了all字段。
复制的原因是:我们并不知道用户进行搜索时是根据酒店的名字还是品牌还是城市进行搜索的,如果在三个字段中进行匹配搜索,对es的开销十分大,所以我们把这三个字段合并成一个all字段,这样一来,用户进行搜索时,我们只需要在all字段进行文档匹配,获取对应的id即可 ,但是对每个字段的是否分词还是原来的形式,如name进行最细分词,brand,city都不进行分词
这里设置了location字段,它的type类型是geo_point类型,这是es提供的包括了维度,精度的数据类型
初始化RestClient
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
1.导入es的RestHighLevelClient依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2.因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本,换成自己的es版本:
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3.初始化RestHighLevelClient,即维护一个bean到IoC容器
//定义操作es的客户端对象
@Bean
public RestHighLevelClient restHighLevelClient(){
return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.230.100:9200")));
}
使用RestHighLevelClient构建索引库及其mapping
代码分为三步:
- 1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
- 2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量HOTEL_MAPPING,让代码看起来更加优雅。
- 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
1.构建json数据常量
public class HotelConstant {
public static final String HOTEL_MAPPING="{\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" +
"}";
}
2.创建索引库
/**
* 测试索引库功能
*/
@SpringBootTest
public class TestIndex {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void test01() throws IOException {
//构建 创建索引库请求对象,参数为索引库对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//设置DSL语句,第一个参数为构建mapping的json语句,第二个参数是表明这是JSON数据格式
request.source(HotelConstant.HOTEL_MAPPING, XContentType.JSON);
//发起请求
CreateIndexResponse result = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
boolean isSuccess = result.isAcknowledged();
System.out.println(isSuccess?"构建索引库成功":"构建索引库失败");
}
}
3.测试是否构建成功
GET /hotel
使用RestHighLevelClient查询索引库和删除索引库
/**
* 判断索引库是否存在
*/
@Test
public void test02() throws IOException {
//构建 获取索引库请求对象,参数为索引库名字
GetIndexRequest request = new GetIndexRequest("hotel");
//查询不用写dsl语句
//直接发起请求,判断索引库是否存在
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists?"hotel索引库存在":"hotel索引库不存在");
}
/**
* 删除索引库
*/
@Test
public void test03() throws IOException {
//构建 删除索引库请求对象,参数为索引库对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
//删除不用写dsl语句
//直接发起请求,删除索引库
AcknowledgedResponse result = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
boolean isSuccess = result.isAcknowledged();
System.out.println(isSuccess?"删除成功":"删除失败");
}
总结
JavaRestClient操作elasticsearch的流程基本类似。核心是restHignLevelClient.indices()方法来获取索引库的操作对象。
索引库操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxIndexRequest。XXX是Create、Get、Delete
- 准备DSL( Create时需要,其它是无参)
- 发送请求。调用RestHighLevelClient.indices().xxx()方法,xxx是create、exists、delete
RestClient操作文档
新增文档
我们要将数据库的酒店数据查询出来,写入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
因此,我们需要定义一个新的类型,与索引库结构吻合:
@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
}
代码实现
@SpringBootTest
public class TestDoc {
@Autowired
private HotelMapper hotelMapper;
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void test01() throws IOException {
//使用mapper去数据库中查询数据
Hotel hotel = hotelMapper.selectById(36934L);
//封装成HotelDoc类
HotelDoc hotelDoc = new HotelDoc(hotel);
//序列化成json数据,当作DSL语句
String jsonDSL = JSON.toJSONString(hotelDoc);
//构建 文档请求对象,第一个参数是索引库名字,第二个是文档id
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId()+"");
//设置DSL语句
request.source(jsonDSL, XContentType.JSON);
//发送请求
IndexResponse result = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(result);
}
}
测试
GET /hotel/_doc/36934
/**
* 获取文档信息
*/
@Test
public void test02() throws IOException {
//构建 查询文档请求对象
GetRequest request = new GetRequest("hotel", "36934");
//查询不用写DSL语句
//直接发送请求
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
//获取响应的json数据
String jsonData = response.getSourceAsString();
//将json数据反序列化成对象
HotelDoc hotelDoc = JSON.parseObject(jsonData, HotelDoc.class);
System.out.println(hotelDoc);
}
查询文档
查询的DSL语句如下:
GET /hotel/_doc/{id}
/**
* 获取文档信息
*/
@Test
public void test02() throws IOException {
//构建 查询文档请求对象
GetRequest request = new GetRequest("hotel", "36934");
//查询不用写DSL语句
//直接发送请求
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
//获取响应的json数据
String jsonData = response.getSourceAsString();
//将json数据反序列化成对象
HotelDoc hotelDoc = JSON.parseObject(jsonData, HotelDoc.class);
System.out.println(hotelDoc);
}
修改文档
修改我们讲过两种方式:
- 全量修改:本质是先根据id删除,再新增
- 增量修改:修改文档中的指定字段值
在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:
- 如果新增时,ID已经存在,则修改
- 如果新增时,ID不存在,则新增
所以我们主要关注增量修改。
与之前类似,也是三步走:
- 1)准备Request对象。这次是修改,所以是UpdateRequest
- 2)准备参数。也就是JSON文档,里面包含要修改的字段
- 3)更新文档。这里调用client.update()方法
/**
* 增量修改文档的部分信息
*/
@Test
public void test03() throws IOException {
//构建 更新文档的请求对象
UpdateRequest request = new UpdateRequest("hotel", "36934");
//编写DSL语句
request.doc("city","天津");
//发送请求
UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
}
删除文档
/**
* 删除文档
*/
@Test
public void test04() throws IOException {
//构建 删除文档请求对象
DeleteRequest request = new DeleteRequest("hotel", "36934");
//直接发送请求
restHighLevelClient.delete(request,RequestOptions.DEFAULT);
}
批量导入文档
案例需求:利用BulkRequest批量将数据库数据导入到索引库中。
步骤如下:
-
利用mybatis-plus查询酒店数据
-
将查询到的酒店数据(Hotel)转换为文档类型数据(HotelDoc)
-
利用JavaRestClient中的BulkRequest批处理,实现批量新增文档
/**
* 批量导入数据
*/
@Test
public void test05() throws IOException {
//使用mapper查询到所有数据
List<Hotel> hotels = hotelMapper.selectList(null);
BulkRequest bulkRequest = new BulkRequest("hotel");
hotels.stream().forEach(hotel -> {
//把hotel变成hotelDoc类对象,并序列化成json数据
String jsonDSL = JSON.toJSONString(new HotelDoc(hotel));
//构建 新增文档请求对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId()+"").source(jsonDSL, XContentType.JSON);
//将请求对象添加到bulkRequest中
bulkRequest.add(request);
});
//发送请求
restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
}
成功导入es
GET /hotel/_search
{
"query": {
"match_all": {}
}
}