java与es8实战之六:用JSON创建请求对象(比builder pattern更加直观简洁)
Posted 程序员欣宸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java与es8实战之六:用JSON创建请求对象(比builder pattern更加直观简洁)相关的知识,希望对你有一定的参考价值。
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本文是《java与es8实战》系列的第六篇,经过前面的实战,咱们初步掌握了一些Java对ES的基本操作,通过发送请求对象(例如CreateIndexResponse)到ES服务端,达到操作ES的目的,但是细心的您可能发现了:请求对象可能很复杂,例如多层对象嵌套,那么用代码来创建这些请求对象也必然不会容易
- 今天的文章,咱们先来体验用代码创建请求对象的不便之处,再尝试ES官方给我们提供的解决之道:用JSON创建请求对象
- 接下来,咱们从一个假设的任务开始
任务安排
- 现在咱们要创建一个索引,此索引记录的是商品信息
- 有一个副本(属于setting部分)
- 共三个分片(属于setting部分)
- 共三个字段:商品名称name(keyword),商品描述description(text),价格price(integer)(属于mapping部分)
- name字段值长为256,超出此长度的字段将不会被索引,但是会存储
- 接下来,咱们在kibana上用JSON创建索引,再写代码创建相同索引,然后对比两种方式的复杂程度
kibana上创建索引
- 如果在kibana上用json来创建,请求内容如下,索引名是product001
PUT product001
"settings":
"number_of_shards": 3,
"number_of_replicas": 1
,
"mappings":
"properties":
"name":
"type": "keyword",
"ignore_above": 256
,
"description":
"type": "text"
,
"price":
"type": "integer"
- 效果如下,符合预期
- 通过eshead观察,也是符合预期
- 可见基于JSON的操作简单明了,接下来看看创建相通索引的代码是什么样子
基于代码创建
- 关于如何连接ES的代码并非本篇重点,而且前面的文章已有详细说明,就不多赘述了
- 首先创建一个API,可以接受外部传来的Setting和Mapping设定,然后用这些设定来创建索引
@Autowired
private ElasticsearchClient elasticsearchClient;
@Override
public void create(String name,
Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn,
Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn) throws IOException
elasticsearchClient
.indices()
.create(c -> c
.index(name)
.settings(settingFn)
.mappings(mappingFn)
);
- 然后就是如何准备Setting和Mapping参数,再调用create方法完成创建,为了让代码顺利执行,我将调用create方法的代码写在单元测试类中,这样后面只需要执行单元测试即可调用create方法
@SpringBootTest
class EsServiceImplTest
@Autowired
EsService esService;
@Test
void create() throws Exception
// 索引名
String indexName = "product002";
// 构建setting时,builder用到的lambda
Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn = sBuilder -> sBuilder
.index(iBuilder -> iBuilder
// 三个分片
.numberOfShards("3")
// 一个副本
.numberOfReplicas("1")
);
// 新的索引有三个字段,每个字段都有自己的property,这里依次创建
Property keywordProperty = Property.of(pBuilder -> pBuilder.keyword(kBuilder -> kBuilder.ignoreAbove(256)));
Property textProperty = Property.of(pBuilder -> pBuilder.text(tBuilder -> tBuilder));
Property integerProperty = Property.of(pBuilder -> pBuilder.integer(iBuilder -> iBuilder));
// // 构建mapping时,builder用到的lambda
Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn = mBuilder -> mBuilder
.properties("name", keywordProperty)
.properties("description", textProperty)
.properties("price", integerProperty);
// 创建索引,并且指定了setting和mapping
esService.create(indexName, settingFn, mappingFn);
- 由于Java API Client中所有对象都统一使用builder pattern的方式创建,这导致代码量略多,例如setting部分,除了setting自身要用Lambda表达式,设置分片和副本的代码也要用Lambda的形式传入,这种嵌套效果在编码中看起来还是有点绕的,阅读起来可能会有点不适应
- 执行单元测试,如下图,未发生异常
- 用kibana查看新建的索引
- 最后,将product001和product002的mapping放在一起对比,可见一模一样
- 再用eshead对比分片和副本的效果,也是一模一样
小结和感慨
- 至此,可以得出结论:
- Java API Client的对ES的操作,能得到kibana+JSON相同的效果
- 然而,用java代码来实现JSON的嵌套对象的内容,代码的复杂程度上升,可读性下降(纯属个人感觉)
- 另外,在开发期间,我们也常常先用kibana+JSON先做基本的测试和验证,然后再去编码
- 因此,如果能在代码中直接使用kibana的JSON,以此取代复杂的builder pattern代码去创建各种增删改查的请求对象,那该多好啊
- ES官方预判了我的预判,在Java API Client中支持使用JSON来构建请求对象
能用JSON的根本原因
-
动手实践之前,有个问题先思考一下
-
刚才咱们写了那么多代码,才能创建出CreateIndexResponse对象(注意代码:elasticsearchClient.indices().create),怎么就能用JSON轻易的创建出来呢?有什么直接证据或者关键代码吗?
-
来看看CreateIndexResponse的builder的源码,集成了父类,也实现了接口,
public static class Builder extends WithJsonObjectBuilderBase<Builder>
implements
ObjectBuilder<CreateIndexRequest>
- 用IDEA查看类图的功能,Builder的继承和实现关系一目了然,注意红色箭头指向的WithJson接口,它是Builder父类实现的接口,也是让CreateIndexResponse可以通过JSON来创建的关键
- 强大的IDEA,可以在上图直接展开WithJson接口的所有方法签名,如下图,一目了然,三个方法三种入参,证明了使用者可以用三种方式将JSON内容传给Builder,再由Builer根据传入的内容生成CreateIndexResponse实例
创建工程
- 在《java与es8实战之二:实战前的准备工作》中创建整了个系列共用的父工程elasticsearch-tutorials,今天新建的新工程名为object-from-json,也属于elasticsearch-tutorials的子工程,pom.xml如下
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 请改为自己项目的parent坐标 -->
<parent>
<artifactId>elasticsearch-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 请改为自己项目的artifactId -->
<artifactId>object-from-json</artifactId>
<packaging>jar</packaging>
<!-- 请改为自己项目的name -->
<name>object-from-json</name>
<url>https://github.com/zq2599</url>
<!--不用spring-boot-starter-parent作为parent时的配置-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>$springboot.version</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 不加这个,configuration类中,IDEA总会添加一些提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- exclude junit 4 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<!-- elasticsearch引入依赖 start -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 使用spring boot Maven插件时需要添加该依赖 -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 需要此插件,在执行mvn test命令时才会执行单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
</project>
- 是个普通的SpringBoot应用,入口类FromJsonApplication.java如下,非常简单
package com.bolingcavalry.fromjson;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FromJsonApplication
public static void main(String[] args)
SpringApplication.run(FromJsonApplication.class, args);
- 然后是连接ES的配置类ClientConfig.java,关于如何连接ES,在《java与es8实战之四》一文已经详细说明,不再赘述,直接使用配置类的elasticsearchClient方法创建的ElasticsearchClient对象即可操作ES
@ConfigurationProperties(prefix = "elasticsearch") //配置的前缀
@Configuration
public class ClientConfig
@Setter
private String hosts;
/**
* 解析配置的字符串,转为HttpHost对象数组
* @return
*/
private HttpHost[] toHttpHost()
if (!StringUtils.hasLength(hosts))
throw new RuntimeException("invalid elasticsearch configuration");
String[] hostArray = hosts.split(",");
HttpHost[] httpHosts = new HttpHost[hostArray.length];
HttpHost httpHost;
for (int i = 0; i < hostArray.length; i++)
String[] strings = hostArray[i].split(":");
httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
httpHosts[i] = httpHost;
return httpHosts;
@Bean
public ElasticsearchClient elasticsearchClient()
HttpHost[] httpHosts = toHttpHost();
RestClient restClient = RestClient.builder(httpHosts).build();
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
// And create the API client
return new ElasticsearchClient(transport);
- 最后是配置文件application.yml
elasticsearch:
# 多个IP逗号隔开
hosts: 127.0.0.1:9200
- 现在工程已经建好,接下来开始实践如何通过JSON得到请求对象,通过刚才对WithJson接口的分析,JSON转请求对象共有三种方式
- ImputStream
- JSON字符串
- Parse
- 接下来逐个实践
第一种:InputStream作为入参
- 最简单的方式莫过通过InputStream转换,InputStream是大家常用到的IO类,相信您已经胸有成竹了,流程如下图
- 开始编码,首先创建一个接口EsService.java,里面有名为create的方法,这是创建索引用的,入参是索引名和包含有JSON内容的InputStream
public interface EsService
/**
* 以InputStream为入参创建索引
* @param name 索引名称
* @param inputStream 包含JSON内容的文件流对象
*/
void create(String name, InputStream inputStream) throws IOException;
- 接下来是重点:EsService接口的实现类EsServiceImpl.java,可见非常简单,只要调用builder的withJson方法,将InputStream作为入参传入即可
@Service
public class EsServiceImpl implements EsService
@Autowired
private ElasticsearchClient elasticsearchClient;
@Override
public void create(String name, InputStream inputStream) throws IOException
// 根据InputStrea创建请求对象
CreateIndexRequest request = CreateIndexRequest.of(builder -> builder
.index(name)
.withJsonjava与es8实战之四:SpringBoot应用中操作es8(无安全检查)
java与es8实战之四:SpringBoot应用中操作es8(无安全检查)