RestClient操作索引库和文档
一、什么是RestClient?
前面我们学习了通过DSL语句操作ES索引库,文档数据,要想通过java代码操作,必须使用ES官方提供的RestClient实现了,ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。
官方文档地址:https://www.elastic.co/guide/en/elastic search/client/index.html,如下:

客户端说明查看:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.15/index.html

Java REST 客户端有两种风格:
- Java Low Level REST Client:官方 Elasticsearch 的低级客户端。它允许与 Elasticsearch cluster通过http。留下请求编组和响应 取消编组给用户。它与所有Elasticsearch版本兼容。
- Java High Level REST Client:Elasticsearch 的官方高级客户端。 基于低级客户端,公开 API 特定方法并注意 的请求编组和响应取消编组。
本次我们使用Java High Level REST Client进行操作索引库和文档
二、环境准备
2.1.创建酒店信息表
准备创建数据酒店信息表:tb_hotel
DROP TABLE IF EXISTS `tb_hotel`; CREATE TABLE `tb_hotel` ( `id` bigint(20) NOT NULL COMMENT '酒店id', `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称', `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址', `price` int(10) NOT NULL COMMENT '酒店价格', `score` int(2) NOT NULL COMMENT '酒店评分', `brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌', `city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市', `star_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻', `business` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈', `latitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度', `longitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度', `pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店图片', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;
导入数据如下:

2.2.创建springboot工程
2.2.1.创建项目
设置项目名称为hotel_demo,

选择spring
2.2.2.导入依赖
创建springboot项目,导入依赖:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.12</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.augus</groupId> <artifactId>hotel-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>hotel-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <elasticsearch.version>7.17.5</elasticsearch.version> </properties> <dependencies> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.17.5</version> </dependency> <!--FastJson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!--添加druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--mybatis-plus在springboot中启动器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!--mysql数据库连接相关依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> <!--spring-boot启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--springboot测试--> <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>
2.2.3.编写配置文件
在application.yml中进行配置如下:
server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 5 min-idle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall,slf4j connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 web-stat-filter: enabled: true url-pattern: "/*" exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" stat-view-servlet: url-pattern: "/druid/*" allow: 127.0.0.1,192.168.8.109 deny: 192.168.1.188 reset-enable: false login-username: admin login-password: 123456 mybatis-plus: configuration: map-underscore-to-camel-case: true type-aliases-package: com.augus.pojo
2.2.3.创建mapper层
在创建com.augus.pojo包下创建tb_hotel表对应的实体类,创建:Hotel.java,代码如下:
package com.augus.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @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; }
由于最后位置是由经纬度实现,但是表中经纬度是两个字段,所以创建一个HotelDoc.java实体类,其余字段不变,新创建一个location字段,将经纬度组合成字符串赋值给location,如下:
package com.augus.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(); } }
2.2.4.创建mapper层
在创建com.augus.mapper包,创建:HotelMapper.java,代码如下:
@Mapper public interface HotelMapper extends BaseMapper<Hotel> { }
2.2.5.创建service层
在创建com.augus.service包,创建:IHotelService 接口代码如下:
package service; import com.augus.pojo.Hotel; import com.baomidou.mybatisplus.extension.service.IService; public interface IHotelService extends IService<Hotel> { }
然后在service创建子包impl,在里面创建实现类HotelServiceImpl,代码如下:
package service.impl; import com.augus.mapper.HotelMapper; import com.augus.pojo.Hotel; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import service.IHotelService; @Service public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {}
三、删除索引库、利用Java Rest Client实现创建、判断索引库是否存在
需求实现:根据之前的酒店表中数据创建索引库,索引库名为hotel,mapping属性根据数据库结构定义。
基本步骤如下:
- 在数据库中创建表,导入数据,创建springboot项目
- 分析数据结构,定义mapping属性
- 初始化Java Rest Client
- 利用Java Rest Client创建索引库
- 利用Java Rest Client删除索引库
- 利用Java Rest Client判断索引库是否存在
3.1.步骤二:分析数据结构
mapping要考虑的问题:字段名、数据类型、是否参与搜索、字段是否分词、如果分词,分词器使用什么?,表结构如下:

注意,latitude和longitude分别是经纬度字段,在elasticsearch,地理位置ES中支持两种地理坐标数据类型:
- geo_point:由纬度(latitude)和经度(longitude)确定的一个点。例如:"30.91161,121.45661"
- geo_shape:有多个geo_point组成的复杂几何图形。例如一条直线,"LINE STRING(-77.0365338.897676,-77.00905138.889939)
在dev_tools编写索引库如下:
# 酒店的mapping PUT /hotel { "mappings": { "properties": { "id":{ "type":"keyword" }, "name":{ "type":"text", "analyzer": "ik_max_word" }, "addres":{ "type": "keyword", "index": false }, "price":{ "type":"integer" }, "score":{ "type":"integer" }, "brand":{ "type":"keyword" }, "city":{ "type":"keyword" }, "starName":{ "type":"keyword" }, "business":{ "type":"keyword" }, "location":{ "type":"geo_point" }, "pic":{ "type": "keyword", "index": false } } } }
注意:
- id字段在数据库表中类型是bigint,但是在elasticsearch中需要设置为keyword类型,通常,
id字段用于唯一标识文档。使用keyword类型可以确保id的值被视为一个完整的、不可分割的字符串,而不是一个可被解析为数字的整数。这样可以避免在处理id时发生意外的类型转换或截断。 上面index属性默认为true,表示会被索引,设置为false时,表示该字段不会被索引。这意味着该字段的内容不会被存储在倒排索引中,因此无法通过该字段进行搜索、聚合或过滤操作。
单个字段搜索效率高还是要比多个字段搜索效率高,显然是一个字段效率高,但是实际需求是用户输入名称、品牌、商圈能搜到,而且多个字段搜索还要性能好怎么解决?
可以通过字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段。示例:
"all":{ "type":"text","analyzer":"ik_max_word" } "brand":{ "type":"keyword", "copy_to":"all" }
并不是把文档拷贝进去,而只是基于它创建倒排索引,将来查的时候看不到这个字段,好像不存在一样,但是搜索却能根据它进行搜索,

3.2.步骤三:RestClient操作索引库-初始化RestClient
3.2.1.引入es的RestHighLevelClient依赖:
导入依赖的版本需要个elasticsearch的版本相同:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.5</version>
</dependency>
3.2.2.因为Spring Boot2.6.12默认的ES版本是7.15.2,所以我们需要覆盖默认的ES版本:
在上面导入依赖后查询maven中信息版本:

在pom.xml中指定依赖如下:
<properties>
<java.version>8</java.version>
<elasticsearch.version>7.17.5</elasticsearch.version>
</properties>
再次查看,版本已经切换过来了,如下:

3.2.3.初始化RestHighLevelClient:
语法如下:
RestHighLevelClient client=new RestHighLevelClient(RestClient.builder( HttpHost.create("http://192.168.42.150:9200") ));
在测试类HotelDemoApplicationTests.java中创建
- @BeforeEach:单元测试里面的注解,提前完成对象的初始化
- @AfterEach:销毁注解
案例代码如下:
package com.augus; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; @SpringBootTest class HotelDemoApplicationTests { //创建成员变量,保存初始化的对象 private RestHighLevelClient client; @BeforeEach void setUp(){ this.client = new RestHighLevelClient( RestClient.builder(HttpHost.create("http://192.168.42.150:9200") )); } @AfterEach void testDown() throws IOException { this.client.close(); } @Test public void testClient() { //测试创建连接是否成功 System.out.println(client); } }
以后写的所有的单元测试,都会先运行BeforeEach 最后运行@AfterEach,执行上面的testClien测试方法执行如下:

3.3.步骤四:RestClient操作索引库-创建索引库
先创建包constants然后创建HotelConstants类,在里面创建MAPPING_TEMPLATE静态属性,保存创建索引库DSL语言如下:
package com.augus.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" + " \"addres\":{\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" + " },\n" + " \"starName\":{\n" + " \"type\":\"keyword\"\n" + " },\n" + " \"business\":{\n" + " \"type\":\"keyword\",\n" + " \"copy_to\": \"all\"\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" + "}"; }
然后再之前的测试类中新建测试方法,用于创建索引库,代码如下:
@Test void testCreateHotelIndex() throws IOException { //1.创建request对象 CreateIndexRequest request = new CreateIndexRequest("hotel"); //2.请求参数 request.source(MAPPING_TEMPLATE, XContentType.JSON); //3.发起请求 //client.indices() 返回的对象中包含索引库操作的所有方法 client.indices().create(request, RequestOptions.DEFAULT); }
执行后,然后进行查询操作如下:

3.4.步骤五:删除索引库、判断索引库是否存在
- 删除索引库代码如下:
@Test void testDeleteHotel() throws IOException { //1.创建request对象 DeleteIndexRequest request = new DeleteIndexRequest("hotel"); //2.发送请求 client.indices().delete(request, RequestOptions.DEFAULT); }
- 判断索引库是否存在
@Test void testExistsHotel() throws IOException { //1.创建对象 GetIndexRequest request = new GetIndexRequest("hotel"); //2.发起请求 boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); //3.输出 System.out.println(exists); } }
先执行删除索引库操作,然后查询,执行结果如下:

四、Rest Client操作文档
利用Java RestClient实现文档的CRUD,去数据库查询酒店数据,导入到hotel索引库,实现酒店数据的CRUD。基本步骤如下:
- 初始化Java RestClient(和之前一致)
- 利用Java RestClient新增酒店数据
- 利用Java RestClient根据id查询酒店数据
- 利用Java RestClient删除酒店数据
- 利用Java RestClient修改酒店数据
4.1.RestClien新建文档
创建测试类:ElasticsearchDocumentTest.java,完成初始化代码如下:
@SpringBootTest public class ElasticsearchDocumentTest { //创建成员变量,保存初始化的对象 private RestHighLevelClient client; @BeforeEach void setUp(){ this.client = new RestHighLevelClient( RestClient.builder(HttpHost.create("http://192.168.42.150:9200") )); } @AfterEach void testDown() throws IOException { this.client.close(); } }
新建文档,代码如下:
@Test void testAddDocument() throws IOException { //根据id从数据库查询酒店数据 Hotel hotel = iHotelService.getById(36934L); //将查询出来hotel对象转换为HotelDoc HotelDoc hotelDoc = new HotelDoc(hotel); //1.准备Request对象 IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString()); //2.准备json文档 request.source(JSON.toJSONString(hotelDoc), XContentType.JSON); //3.发送请求 client.index(request, RequestOptions.DEFAULT); } }
注意:
- 需要把查询出来的对象序列化成json的风格,利用 FastJson里面的API JSON利用方法.toJSONString(),可以帮助我们把对象序列化
4.2.RestClien查询文档
步骤3:根据id查询酒店数据
需要注意,根据id查询到的文档数据是json,需要反序列化为java对,代码如下:
@Test void testSelectById() throws IOException { //1.创建request对象 GetRequest request = new GetRequest("hotel","36934"); //2.发送请求,得到结果 GetResponse response = client.get(request, RequestOptions.DEFAULT); //3.获取结果 String sourceAsString = response.getSourceAsString(); //4.反序列化成HotelDoc对象 HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class); System.out.println(hotelDoc); } }
执行后结果如下:

4.3.RestClien更新文档
步骤4:根据id修改酒店数据,修改文档数据有两种方式:
方式一:全量更新。再次写入id一样的文档(使用之前的新增即可),就会删除旧文档,添加新文档
方式二:局部更新。只更新部分字段,所以下面演示方式二
@Test void testUpdateDocument() throws IOException { //1.创建request UpdateRequest request = new UpdateRequest("hotel", "36934"); //2.准备请求参数 request.doc("price","399", "brand","7天连锁酒店"); //3.发送请求 client.update(request, RequestOptions.DEFAULT); }
执行后查看文档内容,发现price和brand字段的值已经被修改,查看如下:

4.4.RestClien删除文档
步骤5:根据id删除文档数据,代码如下:
@Test void testDeleteDocument() throws IOException { //1.创建request DeleteRequest request = new DeleteRequest("hotel", "36934"); //2.发送请求 client.delete(request,RequestOptions.DEFAULT); }
执行后在查看文档id为36934的记录已经不存在了,如下:

4.5.RestClient批量导入文档
需求:批量查询酒店数据,然后批量导入索引库中,思路:
- 利用mybatis-plus查询酒店数据
- 将查询到的酒店数据(Hotel)转换为文档类型数据(Hotel Doc)
- 利用Java RestClient中的Bulk批处理,实现批量新增文档
案例代码如下:
@Test void testBulkRequest() throws IOException { //1.批量查询酒店数据 List<Hotel> hotels = iHotelService.list(); //2.创建request BulkRequest request = new BulkRequest(); //3.准备参数 for (Hotel hotel : hotels) { //转换成文件类型 HotelDoc HotelDoc hotelDoc = new HotelDoc(hotel); //创建新增文档的request request.add( new IndexRequest("hotel") .id(hotelDoc.getId().toString()) .source(JSON.toJSONString(hotelDoc), XContentType.JSON) ); } //4.发送请求 client.bulk(request,RequestOptions.DEFAULT); }
代码执行后,查看所有数据信息,


浙公网安备 33010602011771号