• ElasticSearch - 基于 JavaRestClient 操作索引库和文档


    目录

    一、RestClient操作索引库

    1.1、RestClient是什么?

    1.2、JavaRestClient 实现创建、删除索引库

    1.2.1、前言

    1.2.1、初始化 JavaRestClient

    1.2.2、创建索引库

    1.2.3、判断索引库是否存在

    1.2.4、删除索引库

    1.3、JavaRestClient 实现文档的 CRUD

    1.3.1、初始化 JavaRestClient 

    1.3.2、添加文档(酒店数据)到索引库

    1.3.3、根据 id 查询酒店数据

    1.3.4、根据 id 修改酒店数据

    1.3.5、根据 id 删除文档数据

    1.3.6、批量导入文档


    一、RestClient操作索引库


    1.1、RestClient是什么?

    前面我们已经了解了如何利用 DSL 语句去操作 es 的索引库和文档,但作为 java 程序员,将来肯定是要通过 java 代码去操作 es 的,那么想要实现这些,就需要通过 es 官方提供的 RestClient 实现.

    RestClient 实际上就是 es 官方提供的各种语言的客户端,他的作用就是帮助我们组装 DSL 语句,然后发送 http 请求给 es 服务器,而我们只需要通过 java 代码将请求发送给客户端,然后客户端就会帮我们来处理剩下的这些事情.

    官方文档地址:Elasticsearch Clients | Elastic

    1.2、JavaRestClient 实现创建、删除索引

    1.2.1、前言

    这里我将以一个 酒店 demo 工程来演示 JavaRestClient 的操作.

    具体来讲,这是一个酒店的数据,创建的 sql 如下:

    1. CREATE TABLE `tb_hotel` (
    2.   `id` bigint(20NOT NULL COMMENT '酒店id',
    3.   `name` varchar(255NOT NULL COMMENT '酒店名称;例:7天酒店',
    4.   `address` varchar(255NOT NULL COMMENT '酒店地址;例:航头路',
    5.   `price` int(10NOT NULL COMMENT '酒店价格;例:329',
    6.   `score` int(2NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
    7.   `brand` varchar(32NOT NULL COMMENT '酒店品牌;例:如家',
    8.   `city` varchar(32NOT NULL COMMENT '所在城市;例:上海',
    9.   `star_name` varchar(16DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
    10.   `business` varchar(255DEFAULT NULL COMMENT '商圈;例:虹桥',
    11.   `latitude` varchar(32NOT NULL COMMENT '纬度;例:31.2497',
    12.   `longitude` varchar(32NOT NULL COMMENT '经度;例:120.3925',
    13.   `pic` varchar(255DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
    14.   PRIMARY KEY (`id`)
    15. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    之后我们创建 索引库 的时候,就需要基于上述 sql 数据,来考虑 mapping 约束.

    1.2.1、初始化 JavaRestClient

    a)引入 es 的 RestHighLevelClient 依赖

    1. <dependency>
    2. <groupId>org.elasticsearch.clientgroupId>
    3. <artifactId>elasticsearch-rest-high-level-clientartifactId>
    4. dependency>

    b)由于 SpringBoot 默认的 ES 版本是 7.6.2,因此这里我们需要覆盖默认的 ES 版本.

    在 yml 配置文件中添加如下版本信息即可.

    1. <properties>
    2. <java.version>1.8java.version>
    3. <elasticsearch.version>7.12.1elasticsearch.version>
    4. properties>

    c)初始化 RestHighLevelClient.

    这里我们创建一个测试类 HotelIndexTest ,用来演示 RestClient 操作的相关方法.

    1. @SpringBootTest
    2. class HotelIndexTest {
    3. private RestHighLevelClient client;
    4. @BeforeEach
    5. public void setUp() {
    6. client = new RestHighLevelClient(RestClient.builder(
    7. HttpHost.create("http//云服务器ip:9200")
    8. //将来如果是集群,这里还可以通过 HttpHost.create 继续连接多个节点
    9. ));
    10. }
    11. @AfterEach
    12. public void tearDown() throws IOException {
    13. client.close();
    14. }
    15. }

    1.2.2、创建索引库

    这里就需要根据前面提供的表结构来考虑 mapping 该如何建立.

    具体的要考虑:字段名、数据类型、是否参与搜索、是否分词、如果分词,分词器是什么?

    这里可以先使用 Kibana 来编写.

    1. PUT /hotel
    2. {
    3. "mappings": {
    4. "properties": {
    5. "id": {
    6. // id 按照数据库那边的定义,这里因该类型设置为 long
    7. // 但是这里比较特殊,在索引库中 id 比较特殊,将来都是字符串类型.
    8. // 又因为 id 将来不做分词处理,因此是 keyword 类型
    9. // id 将来肯定要参与 crud ,因此 index 就默认为 true 即可.
    10. "type": "keyword"
    11. },
    12. "name": {
    13. // 酒店的名字需要搜索和分词.
    14. "type": "text",
    15. "analyzer": "ik_max_word", "copy_to": "all"
    16. },
    17. "address": {
    18. // 有时候我们需要根据地址来查询附近的酒店,分词也是有必要的("例如 徐汇龙华西路315弄58号")
    19. "type": "text",
    20. "analyzer": "ik_max_word",
    21. "copy_to": "all"
    22. },
    23. "price": {
    24. //将来要根据价格范围过滤酒店,所以需要搜索,分词就没必要了.
    25. "type": "integer"
    26. },
    27. "score": {
    28. //这里就和 price 一样了
    29. "type": "integer"
    30. },
    31. "brand": {
    32. //酒店的品牌肯定是不需要分词了,但一定需要参与搜索.
    33. "type": "keyword",
    34. "copy_to": "all"
    35. },
    36. "city": {
    37. //城市名字不要分词,但需要参与搜索
    38. "type": "keyword",
    39. "copy_to": "all"
    40. },
    41. "star_name": {
    42. //一星、二星、三星... 分词是没有意义的,组合起来才有意义.
    43. //有的人就想住5星酒店,那肯定要参与搜索.
    44. "type": "keyword"
    45. },
    46. "business": {
    47. //商圈比如: 虹桥、外滩... 这些肯定不需要分词,但一定需要参与搜索.
    48. "type": "keyword",
    49. "copy_to": "all"
    50. },
    51. "pic": {
    52. //图片这里就是一个 url 路径,不需要分词,也没有人会搜这个 url
    53. //因此就这个 url 就可以当作关键字来处理.
    54. "type": "keyword",
    55. "index": false
    56. },
    57. "location": {
    58. //在 es 中有两种特殊的方式,专门来表示地理坐标
    59. //"geo_point": 表示地图上的点
    60. //"geo_shape": 表示地图上的区域,也就是多个点组成.
    61. //那么酒店肯定是属于一个点(毕竟从地球上看,再大的酒店也不过是点)
    62. // geo_point 里面由 经度 和 纬度 组成,并且是这两拼在一起组成的字符串
    63. "type": "geo_point"
    64. },
    65. "all": {
    66. // 将来 name、address、brand... 这些字段大概率都需要参与搜索
    67. // 也就意味着用户输入的的关键字,我们后端都需要根据多个字来搜.
    68. // 并且我们可以想象以下 es 作搜索的时候, 根据多个字段去搜索的效率肯定是要比一个字段搜索效率要低
    69. //这里对比以下数据库就清楚了.
    70. //最重要的是, 我们也希望用户输入名称就能搜到相关的内容, 用户输入品牌也能搜到相关内容...
    71. // es 就中有一个字段 "copy_to", 就是将当前字段的值拷贝到指定字段.
    72. //这里我们就将需要搜索的字段都拷贝到 all 这个字段中就 ok
    73. //这也就实现了在一个字段里, 搜索到多个字段的内容.
    74. "type": "text",
    75. "analyzer": "ik_max_word"
    76. }
    77. }
    78. }
    79. }

    自定义 all 字段的解读: 

    将来 name、address、brand... 这些字段大概率都需要参与搜索,也就意味着用户输入的的关键字,我们后端都需要根据多个字来搜,并且我们可以想象以下 es 作搜索的时候, 根据多个字段去搜索的效率肯定是要比一个字段搜索效率要低,这里对比以下数据库就清楚了~

    最重要的是, 我们也希望用户输入名称就能搜到相关的内容, 用户输入品牌也能搜到相关内容... es 就中有一个字段 "copy_to", 就是将当前字段的值拷贝到指定字段。这里我们就将需要搜索的字段都拷贝到 all 这个字段中就 ok ,实现了在一个字段里, 搜索到多个字段的内容.

    而且这里还做了优化,并不是真的吧文档拷贝进去,而是创建索引,将来你去查的时候,是看不到这些字段,但搜却能搜到(类似于根据指针找到数据所在位置).

    copy_to 用法如下:

    1. 拷贝到单个字段中:"copy_to": "all"  (拷贝到 all 字段中)
    2. 拷贝到多个字段中:"copy_to": ["all", "suggestion"]   (拷贝到 all 和 suggestion 字段中)

    创建索引库代码如下:

    1. @Test
    2. public void testCreateHotelIndex() throws IOException {
    3. //1.创建 Request 对象
    4. CreateIndexRequest request = new CreateIndexRequest("hotel");
    5. //2.编写请求参数(MAPPING_TEMPLATE 是一个静态常量,内容是创建索引库的 DSL 语句)
    6. request.source(MAPPING_TEMPLATE, XContentType.JSON);
    7. //3.发起请求
    8. client.indices().create(request, RequestOptions.DEFAULT);
    9. }
    • CreateIndexRequest 的构造参数就是请求创建的索引库的名字.
    • MAPPING_TEMPLATE:是自定义的静态常量,内容是创建索引库的 DSL 语句.
    • client.indices(): 这个方法的返回值是一个对象(indices 是 index 的复数形式),包含了操作索引库的所有方法.
    • RequestOptions.DEFAULT :就表示走默认的方法.

    执行以后发现运行成功了~

    之后去 Elastic DevTools 上去 GET,就可以看到新增的索引库了~

    1.2.3、判断索引库是否存在

    判断索引库是否存在代码如下:

    1. @Test
    2. public void testExistsHotelIndex() throws IOException {
    3. //1.创建 Request 对象
    4. GetIndexRequest request = new GetIndexRequest("hotel");
    5. //2.发送请求
    6. boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    7. System.out.println(exists);
    8. }

    很多时候,我们先写 client.indices().exists 就可以之间看出需要什么参数

    运行以后,可以看到通过了(true 是因为上个案例添加索引库是存在的).

    1.2.4、删除索引库

    判断删除索引库代码如下:

    1. @Test
    2. public void testDeleteHotelIndex() throws IOException {
    3. //1.创建 Request 对象
    4. DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    5. //2.发送请求
    6. client.indices().delete(request, RequestOptions.DEFAULT);
    7. }

    之后再查询就发现查询不到了,表明删除成功.

    1.3、JavaRestClient 实现文档的 CRUD

    1.3.1、初始化 JavaRestClient 

    这里的初始化操作和操作索引库的初始化一样(本质上都是连接 JavaRestClient 客户端).

    1. @SpringBootTest
    2. class HotelDocumentTest {
    3. private RestHighLevelClient client;
    4. @BeforeEach
    5. public void setUp() {
    6. client = new RestHighLevelClient(RestClient.builder(
    7. HttpHost.create("http://云服务器ip:9200")
    8. ));
    9. }
    10. @AfterEach
    11. public void tearDown() throws IOException {
    12. client.close();
    13. }
    14. }

    1.3.2、添加文档(酒店数据)到索引库

    Ps:操作文档前需要先创建对应索引库

    这里我先通过 MyBatis-Puls 从数据库拿到数据,然后添加文档.

    实体类如下(这里重写构造方法主要是为了 location 属性(地理位置),将经度,纬度合二为一):

    1. @Data
    2. @NoArgsConstructor
    3. public class HotelDoc {
    4. private Long id;
    5. private String name;
    6. private String address;
    7. private Integer price;
    8. private Integer score;
    9. private String brand;
    10. private String city;
    11. private String starName;
    12. private String business;
    13. private String location;
    14. private String pic;
    15. public HotelDoc(Hotel hotel) {
    16. this.id = hotel.getId();
    17. this.name = hotel.getName();
    18. this.address = hotel.getAddress();
    19. this.price = hotel.getPrice();
    20. this.score = hotel.getScore();
    21. this.brand = hotel.getBrand();
    22. this.city = hotel.getCity();
    23. this.starName = hotel.getStarName();
    24. this.business = hotel.getBusiness();
    25. this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
    26. this.pic = hotel.getPic();
    27. }
    28. }

    @NoArgsConstructor:生成无参构造.

    编写添加文档代码:

    1. @Test
    2. public void testAddDocument() throws IOException {
    3. //1.获取酒店数据
    4. Hotel hotel = hotelService.getById(5865979L);
    5. //2.转化文档(主要是地理位置)
    6. HotelDoc hotelDoc = new HotelDoc(hotel);
    7. //3.转化为 JSON 格式
    8. String hotelJson = objectMapper.writeValueAsString(hotelDoc);
    9. //4.构造请求
    10. IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
    11. //5.添加请求参数(json 格式)
    12. request.source(hotelJson, XContentType.JSON);
    13. //6.发送请求
    14. client.index(request, RequestOptions.DEFAULT);
    15. }

    运行后发现通过了

    在 Kibana 上查询就可以得到对应的数据

    1.3.3、根据 id 查询酒店数据

    这里值得注意的是:通过 client.get 查询到的是一个 GetResponse 对象,需要获取里面的原数据.

    代码如下:

    1. @Test
    2. public void testGetDocument() throws IOException {
    3. //1.构造请求
    4. GetRequest request = new GetRequest("hotel").id("5865979");
    5. //2.发送请求
    6. GetResponse response = client.get(request, RequestOptions.DEFAULT);
    7. //3.转化成json
    8. String json = response.getSourceAsString();
    9. System.out.println(json);
    10. }

    运行后就可以得到对应的数据

    1.3.4、根据 id 修改酒店数据

    修改文档数据有两种方式(之前提到过):

    • 全量更新(就是上面演示的添加文档):再次写入 id 一样的文档,就会删除旧文档,添加新文档.
    • 局部更新(演示这个):只更新部分字段.
    1. @Test
    2. public void testUpdateDocument() throws IOException {
    3. //1.构造请求
    4. UpdateRequest request = new UpdateRequest("hotel", "5865979");
    5. //2.填写参数
    6. request.doc(
    7. "name", "地表最强酒店",
    8. "price", "99999"
    9. );
    10. //3.发送请求
    11. client.update(request, RequestOptions.DEFAULT);
    12. }

    注意:request.doc 的参数中不可以直接传递对象,可以将对象先转化为 JSON 格式,然后在 doc 参数中通过 XContentType 指定 JSON 类型即可,如下

    1. public void updateByArticleId(Long articleId) {
    2. //1.创建修改请求
    3. UpdateRequest request = new UpdateRequest(ESConstants.INDEX_ARTICLE, articleId.toString());
    4. //2.填写修改参数
    5. //1) 远程调用获取修改后的文章信息
    6. ArticleESVO articleESVO = getESVOByArticleId(articleId);
    7. String result = objectMapper.writeValueAsString(articleESVO);
    8. //2) 修改文档
    9. request.doc(result, XContentType.JSON);
    10. //2.发送修改请求
    11. client.update(request, RequestOptions.DEFAULT);
    12. }

    在 Kibana 上通过 GET 查询如下:

    1.3.5、根据 id 删除文档数据

    删除文档代码如下:

    1. @Test
    2. public void testDeleteDocument() throws IOException {
    3. //1.构造请求
    4. DeleteRequest request = new DeleteRequest("hotel", "5865979");
    5. //2.发送请求
    6. client.delete(request, RequestOptions.DEFAULT);
    7. }

    1.3.6、批量导入文档

    例如导入酒店的所有数据,代码如下:

    1. @Test
    2. public void testBulkDocument() throws IOException {
    3. //1.获取酒店所有数据
    4. List hotelList = hotelService.list();
    5. //2.构造请求
    6. BulkRequest request = new BulkRequest();
    7. //3.准备参数
    8. for(Hotel hotel : hotelList) {
    9. //转化为文档(主要是地理位置)
    10. HotelDoc hotelDoc = new HotelDoc(hotel);
    11. String json = objectMapper.writeValueAsString(hotelDoc);
    12. request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));
    13. }
    14. //4.发送请求
    15. client.bulk(request, RequestOptions.DEFAULT);
    16. }

    运行后可以看到通过了

    之后再 Kibana 上随机查询一个酒店数据都是存在的

  • 相关阅读:
    牛客网AI面试题目
    目标检测YOLO实战应用案例100讲-基于改进YOLO v7的智能振动分拣系统开发
    bat一键给windows server 2012 打补丁
    mysql——面试题初体验
    leetcode 39. 组合总和(完全背包问题)
    Hikyuu 1.3.0 发布,高性能量化交易研究框架
    AS-V1000 视频监控平台产品介绍:客户端功能介绍(三)
    LeetCode算法心得——连续的子数组和(前缀和+HashMap)
    用HTML+CSS做一个简单好看的校园社团网页
    cas:174501-65-6|1-丁基-3-甲基咪唑四氟硼酸盐[C4MIm]BF4离子液体|颜色:Clearyellow-orange
  • 原文地址:https://blog.csdn.net/CYK_byte/article/details/133255773