这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
如果在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"
}
}
}
}
效果如下,符合预期
关于如何连接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的形式传入,这种嵌套效果在编码中看起来还是有点绕的,阅读起来可能会有点不适应
执行单元测试,如下图,未发生异常
动手实践之前,有个问题先思考一下
刚才咱们写了那么多代码,才能创建出CreateIndexResponse对象(注意代码:elasticsearchClient.indices().create),怎么就能用JSON轻易的创建出来呢?有什么直接证据或者关键代码吗?
来看看CreateIndexResponse的builder的源码,集成了父类,也实现了接口,
public static class Builder extends WithJsonObjectBuilderBase
implements
ObjectBuilder
用IDEA查看类图的功能,Builder的继承和实现关系一目了然,注意红色箭头指向的WithJson接口,它是Builder父类实现的接口,也是让CreateIndexResponse可以通过JSON来创建的关键
在《java与es8实战之二:实战前的准备工作》中创建整了个系列共用的父工程elasticsearch-tutorials,今天新建的新工程名为object-from-json,也属于elasticsearch-tutorials的子工程,pom.xml如下
<!--不用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>
是个普通的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转请求对象共有三种方式
开始编码,首先创建一个接口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)
.withJson(inputStream));elasticsearchClient.indices().create(request);
}
}
为了验证EsServiceImpl的create方法,先准备好json文件,文件名为product003.json,完整路径是:/Users/will/temp/202206/25/product003.json
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name": {
"type": "keyword",
"ignore_above": 256
},
"description": {
"type": "text"
},
"price": {
"type": "integer"
}
}
}
}
最后写一个单元测试类,调用EsServiceImpl的create方法,将product003.json转成InputStream对象作为其入参,验证create方法的功能是否符合预期,如下所示,代码非常简单
@Test
void createByInputStream() throws Exception {
// 文件名
String filePath = "/Users/will/temp/202206/25/product003.json";
// 索引名
String indexName = "product003";
// 通过InputStream创建索引
esService.create(indexName, new FileInputStream(filePath));
}
运行单元测试代码,一切正常
接下来尝试WithJson接口的第二个方法
default T withJson(Reader input) {
JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
return withJson(mapper.jsonProvider().createParser(input), mapper);
}
先来看看这个Reader的继承关系,本篇不会详细分析Reader代码,咱们重点关注它的两个比较重要的子类:StringReader和FileReader
首先,给EsService接口新增一个方法
/**
* 以Reader为入参创建索引
* @param name 索引名称
* @param reader 包含JSON内容的文件流对象
*/
void create(String name, Reader reader) throws IOException;
接下来是重点:EsService接口的实现类EsServiceImpl.java,可见非常简单,只要调用builder的withJson方法,将Reader作为入参传入即可
@Override
public void create(String name, Reader reader) throws IOException {
// 根据Reader创建请求对象
CreateIndexRequest request = CreateIndexRequest.of(builder -> builder
.index(name)
.withJson(reader));elasticsearchClient.indices().create(request);
}
json文件继续使用刚才创建的product003.json文件
单元测试代码中也增加一个方法,用于验证刚才写的create方法
@Test
void createByReader() throws Exception {
// 文件名
String filePath = "/Users/will/temp/202206/25/product003.json";
// 索引名
String indexName = "product004";// 通过InputStream创建索引
esService.create(indexName, new FileReader(filePath));
}
接下来是执行单元测试方法,在kibana和eshead上验证product004索引和之前新建的几个索引是否一致,这里就不多占用篇幅了,结论是一模一样
其实吧,用InputStream或者Reader作为参数,内部实现是一回事,来看看FileReader构造方法的源码吧,里面是InputStream
public class FileReader extends InputStreamReader {
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName));
}
接下来要验证的是用字符串来创建请求对象,这个比较实用,用字符串创建请求对象,给我们的应用开发提供了很大的自由度,废话少说,开始写代码
首先还是给EsService接口新增一个方法,入参是索引名称和JSON字符串
/**
* 以字符串为入参创建索引
* @param name 索引名称
* @param jsonContent 包含JSON内容的字符串
*/
void create(String name, String jsonContent) throws IOException;
接下来是重点:EsService接口的实现类EsServiceImpl.java,可见非常简单,用字符串创建StringReader对象,然后只要调用builder的withJson方法,将StringReader对象作为入参传入即可
@Override
public void create(String name, String jsonContent) throws IOException {
// 根据Reader创建请求对象
CreateIndexRequest request = CreateIndexRequest.of(builder -> builder
.index(name)
.withJson(new StringReader(jsonContent)));elasticsearchClient.indices().create(request);
}
为了验证上面的create方法,在单元测试类中新增一个方法来验证
@Test
void createByString() throws Exception {
// 文件名
String jsonContent = "{\n" +
" \"settings\": {\n" +
" \"number_of_shards\": 3,\n" +
" \"number_of_replicas\": 1\n" +
" },\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"name\": {\n" +
" \"type\": \"keyword\",\n" +
" \"ignore_above\": 256\n" +
" },\n" +
" \"description\": {\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"price\": {\n" +
" \"type\": \"integer\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";// 索引名
String indexName = "product005";
// 通过InputStream创建索引
esService.create(indexName, jsonContent);
}
接下来是执行单元测试方法,在kibana和eshead上验证product004索引和之前新建的几个索引是否一致,这里就不多占用篇幅了,结论是一模一样
基于JSON创建ES请求对象的最后一种方法如下,入参是JsonParser和JsonpMapper
T withJson(JsonParser parser, JsonpMapper mapper)
前面三种方法,咱们都写了代码去验证,不过最后这种就不写代码验证了,原因很简单:没必要,咱们先来看看WithJson接口的源码
public interface WithJson
default T withJson(InputStream input) {
JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
return withJson(mapper.jsonProvider().createParser(input), mapper);
}
default T withJson(Reader input) {
JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
return withJson(mapper.jsonProvider().createParser(input), mapper);
}
T withJson(JsonParser parser, JsonpMapper mapper);
}
可见,前面使用过的withJson(InputStream input)和withJson(Reader input),其实都是在调用withJson(JsonParser parser, JsonpMapper mapper),所以,在实际使用中,掌握withJson(InputStream input)和withJson(Reader input)就已经够用了,如果一定要使用withJson(JsonParser parser, JsonpMapper mapper),就参考上面的代码去构造JsonParser即可
有时候用代码和JSON混合使用来创建请求对象,既能用JSON省去大量代码工作,又能用代码保持该有的灵活性,如下所示,查询用JSON字符串,聚合参数用builder的API生成
Reader queryJson = new StringReader(
"{" +
" \"query\": {" +
" \"range\": {" +
" \"@timestamp\": {" +
" \"gt\": \"now-1w\"" +
" }" +
" }" +
" }" +
"}");
SearchRequest aggRequest = SearchRequest.of(b -> b
.withJson(queryJson)
.aggregations("max-cpu", a1 -> a1
.dateHistogram(h -> h
.field("@timestamp")
.calendarInterval(CalendarInterval.Hour)
)
.aggregations("max", a2 -> a2
.max(m -> m.field("host.cpu.usage"))
)
)
.size(0)
);
Map
.search(aggRequest, Void.class)
.aggregations();
另外,不光是请求对象,与请求对象有关的实例也能用JSON生成,回顾本文最开始的那段代码中,构造CreateIndexResponse对象时还要创建Property对象,实际上这个Property是可以通过JSON生成的,参考代码如下
String json = "{ " +
" \"type\": \"text\"," +
" \"fields\": {" +
" \"some_field\": { " +
" \"type\": \"keyword\"," +
" \"normalizer\": \"lowercase\"" +
" }" +
" }" +
" }";
Property p = Property.of(b -> b
.withJson(new StringReader(json))
);
至此,基于JSON构造ES请求对象的实战就完成了,今后在kibana上验证通过的JSON请求体,可以直接放在代码中用于使用,这将有效的降低代码量,也提升了整体可读性
名称
链接
备注
项目主页
https://github.com/zq2599/blog_demos
该项目在GitHub上的主页
git仓库地址(https)
https://github.com/zq2599/blog_demos.git
该项目源码的仓库地址,https协议
git仓库地址(ssh)
git@github.com:zq2599/blog_demos.git
该项目源码的仓库地址,ssh协议
这个git项目中有多个文件夹,本次实战的源码在elasticsearch-tutorials文件夹下,如下图红框
elasticsearch-tutorials是个父工程,里面有多个module,本篇实战的module是object-from-json,如下图红框
手机扫一扫
移动阅读更方便
你可能感兴趣的文章