Java Client

简介: 本教程介绍如何使用Elasticsearch 7.17.x的新版Java Client配置客户端、创建索引、映射分析及增删改查文档。通过商城搜索场景,演示索引映射设计、Java模型类构建、批量导入数据等操作,并解决LocalDateTime序列化等问题,提升开发效率。

4.1. 配置Java client

前边我们都是在Kibana中使用DSL语法直接请求Elasticsearch的HTTP 接口进行测试,DSL是Elasticsearch的领域特定语言(Domain Specific Language, DSL)是一种基于JSON的查询语言。

在项目开发中为了提高开发效率ES官方提供了各种不同语言的客户端,这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址

由于ES目前最新版本是8.x,提供了全新版本的客户端Java Client,老版本的客户端Java REST Client已经被标记为过时。

我们使用的是7.17.x版本,新版本和老版本都支持,将来的版本会全面抛弃老版本的Java REST Client ,所以本教程使用新版本的Java Client

Java Client要求:

  • Java 8 或更高版本。
  • JSON 对象映射库,可将您的应用程序类与 Elasticsearch API 无缝集成。Java 客户端支持JacksonJSON-B库(如 Eclipse Yasson)

如何集成Java Client,可参考文档:地址链接

hmall-parent中添加依赖管理:

<properties>
  <es.version>7.17.7</es.version>
  <jackson.version>2.13.0</jackson.version>
  <jakarta.json-ai.version>2.0.1</jakarta.json-ai.version>
</properties>
<!-- 对依赖包进行管理 -->
<dependencyManagement>
  <dependencies>
    <!--es-->
    <dependency>
      <groupId>co.elastic.clients</groupId>
      <artifactId>elasticsearch-java</artifactId>
      <version>${es.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.json</groupId>
      <artifactId>jakarta.json-api</artifactId>
      <version>${jakarta.json-ai.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

在hmall-item添加如下依赖:

<dependency>
  <groupId>co.elastic.clients</groupId>
  <artifactId>elasticsearch-java</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
  <groupId>jakarta.json</groupId>
  <artifactId>jakarta.json-api</artifactId>
</dependency>

这里为了单元测试方便,我们创建一个测试类IndexTest,然后将初始化的代码编写在@BeforeEach方法中:参考:链接

package com.hmall.item.es;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class IndexTest {
    private ElasticsearchClient esClient;
    private RestClient restClient;
    @BeforeEach
    void setUp() {
        // Create the low-level client
        this.restClient = RestClient.builder(
            new HttpHost("192.168.101.68", 9200)).build();
        // Create the transport with a Jackson mapper
        ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
        // And create the API client
        this.esClient = new ElasticsearchClient(transport);
    }
    @AfterEach
    void tearDown() throws IOException {
        this.restClient.close();
    }
}

4.2. 创建索引

下边我们以商城项目为例,使用Java Client维护索引数据。搜索页面的效果如图所示:

最终我们使用Elaticsearch实现搜索接口。

4.2.1 分析索引的映射

以下分析过程非常重要,强烈建议:自己根据上面的页面完成独立分析、落地工作

首先我们需要创建索引,配置映射,首先针对上图去分析映射结构,包括哪些字段以及字段的类型等属性。

实现搜索功能需要的字段包括三大部分:

  • 搜索关键字字段:
  • 商品名称
  • 过滤字段
  • 分类
  • 品牌
  • 价格
  • 排序字段
  • 默认:按照更新时间降序排序
  • 销量
  • 价格
  • 展示字段
  • 商品id:用于点击后跳转
  • 图片地址
  • 是否是广告推广商品
  • 名称
  • 价格
  • 评价数量
  • 销量

对应的商品表结构如下,索引库无关字段已经划掉:

结合数据库表结构,以上字段对应的mapping映射属性如下:

字段名

字段类型

类型说明

是否

参与搜索

是否

参与分词

分词器

id

long

长整数

——

name

text

字符串,参与分词搜索

IK

price

integer

以分为单位,所以是整数

——

stock

integer

字符串,但是不分词

——

image

keyword

字符串,但是不分词

——

category

keyword

字符串,但是不分词

——

brand

keyword

字符串,但是不分词

——

sold

integer

销量,整数

——

commentCount

integer

评价,整数

——

isAD

boolean

布尔类型

——

updateTime

Date

更新时间

——

4.2.2 创建索引

根据分析我们使用下边的语句创建items索引:

PUT /items
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "price":{
        "type": "integer"
      },
      "stock":{
        "type": "integer"
      },
      "image":{
        "type": "keyword",
        "index": false
      },
      "category":{
        "type": "keyword"
      },
      "brand":{
        "type": "keyword"
      },
      "sold":{
        "type": "integer"
      },
      "commentCount":{
        "type": "integer",
        "index": false
      },
      "isAD":{
        "type": "boolean"
      },
      "updateTime":{
        "type": "date"
      }
    }
  }
}

我们为什么不用Java Client去创建索引呢?

这就好比在MySQL中创建表,通常我们使用DDL语句通过MySQL客户端执行,而对于数据的CRUD及复杂的SQL语句我们会通过jdbc 去访问mysql数据库一样。

所以,使用Elasticsearch通常我们使用Kibana通过DSL语句去创建索引,而不会使用Java Client去创建索引,使用Java Client主要是为了向索引中添加文档、从索引中搜索文档。

4.3. 新增文档

接下来我们使用Java Client向索引中添加文档。

Java Client的使用方法可以参考 文档 学习

4.3.1 创建模型类

就和使用MyBatis一样操作数据库需要一个模型对象,使用Elasticsearch向索引执行CRUD操作也需要模型类。

依据索引映射创建模型类:

小技巧:创建模型类可以提供索引映射由AI去生成模型类

package com.hmall.item.domain.po;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "索引库实体")
public class ItemDoc {
    @ApiModelProperty("商品id")
    private String id;
    @ApiModelProperty("商品名称")
    private String name;
    @ApiModelProperty("价格(分)")
    private Integer price;
    @ApiModelProperty("库存")
    private Integer stock;
    @ApiModelProperty("商品图片")
    private String image;
    @ApiModelProperty("类目名称")
    private String category;
    @ApiModelProperty("品牌名称")
    private String brand;
    @ApiModelProperty("销量")
    private Integer sold;
    @ApiModelProperty("评论数")
    private Integer commentCount;
    @ApiModelProperty("是否是推广广告,true/false")
    private Boolean isAD;
    @ApiModelProperty("更新时间")
    private LocalDateTime updateTime;
}

4.3.2 编写客户端代码

下边参考ES文档编写客户端代码。

@SpringBootTest
@Slf4j
public class IndexTest {
    ...
    @Autowired
    private IItemService itemService;
    
    @Test
    void testAddDocument() throws IOException {
        //商品id
        Long id = 100002644680L;
        // 1.根据id查询商品数据
        Item item = itemService.getById(id);
        // 2.转换为文档类型
        ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
        IndexResponse response = esClient.index(i -> i
                .index("items")//指定索引名称
                .id(itemDoc.getId())//指定主键
                .document(itemDoc)//指定文档对象
        );
        //结果
        String s = response.result().jsonValue();
        log.info("result:"+s);
    
    }
}

4.3.3 测试

下边进行运行测试方法。报错:

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.hmall.item.domain.po.ItemDoc["updateTime"])

根据提示猜测是数据绑定出问题,现在是要把Java 对象的信息映射为ES的索引文档,jackson-datatype-jsr310对于LocalDateTime不支持。

使用AI解决:

elasticsearch使用的是7.17.7,使用co.elastic.clients.elasticsearch.ElasticsearchClient 向索引新增文档报错如下:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.hmall.item.domain.po.ItemDoc["updateTime"])

根据AI提示修改如下:

添加 JavaTimeModule 以支持 LocalDateTime 类型。

这个类引包别整错了:package com.fasterxml.jackson.databind;

完整代码如下:

package com.hmall.item.es;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.json.JSONUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.AcknowledgedResponse;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch.core.InfoRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.PutMappingResponse;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.hmall.item.domain.po.Item;
import com.hmall.item.domain.po.ItemDoc;
import com.hmall.item.service.IItemService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@SpringBootTest
@Slf4j
public class IndexTest {
    private ElasticsearchClient esClient;
    private RestClient restClient;
    @Autowired
    private IItemService itemService;
    @BeforeEach
    void setUp() {
        // Create the low-level client
        this.restClient = RestClient.builder(
            new HttpHost("192.168.101.68", 9200)).build();
        // 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();
        // 添加 JavaTimeModule 以支持 LocalDateTime 类型
        objectMapper.registerModule(new JavaTimeModule());
        // Create the transport with a Jackson mapper
        ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper(objectMapper));
        // And create the API client
        this.esClient = new ElasticsearchClient(transport);
    }
    //创建文档
    @Test
    void testAddDocument() throws IOException {
        // 1.根据id查询商品数据
        Item item = itemService.getById(100002644680L);
        // 2.转换为文档类型
        ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
        IndexResponse response = esClient.index(i -> i
                                                .index("items")//指定索引名称
                                                .id(itemDoc.getId())//指定主键
                .document(itemDoc)//指定文档对象
        );
        //结果
        String s = response.result().jsonValue();
        log.info("result:"+s);
    }
    @AfterEach
    void tearDown() throws IOException {
        this.restClient.close();
    }
}

执行成功进行验证

我们使用DSL查询:

GET /items/_search

结果:

确定ES的索引中存在刚才添加的文档。

4.3.4 错误修复

这里我运行单测,提示seata相关错误,如下

解决方案:

  • 注释pom文件中seata依赖,重启测试即可

4.4 查询文档

参考文档:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/reading.html

通过阅读文档可知,这里实现的是根据ID查询文档,编写代码:

@Test
void testGetDocumentById() throws IOException {
GetResponse<ItemDoc> response = esClient.get(g -> g
                                             .index("items")
                                             .id("100002644680"),
                                             ItemDoc.class
                                            );
if (response.found()) {
    ItemDoc itemDoc = response.source();
    log.info("itemDoc: " + itemDoc);
} else {
    log.info ("itemDoc not found");
}
}

4.5 删除文档

通过两个API方法的学习:

  • 新增文档:esClient.index()方法
  • 查询文档:esClient.get()方法

对于删除文档的方式可以根据代码提示自行编写,如下:

@Test
void testDeleteDocumentById() throws IOException {
    DeleteResponse response = esClient.delete(d -> d
                                              .index("items")
                                              .id("100002644680")
                                             );
    String s = response.result().jsonValue();
    log.info("result:"+s);
}

4.6 修改文档

  1. 局部修改

如果要更新的文档不存在会报错。

@Test
void testUpdateDocumentById() throws IOException {
    //更新对象
    ItemDoc itemDoc = new ItemDoc();
    itemDoc.setName("更新名称");
    UpdateResponse<ItemDoc> response = esClient.update(u -> u
                                                       .index("items")
                                                       .id("100002644680")
                                                       .doc(itemDoc), ItemDoc.class);
    String s = response.result().jsonValue();
    log.info("result:"+s);
}
  1. 有则更新,没有则添加。
@Test
void testUpdateDocumentById2() throws IOException {
    //更新对象
    ItemDoc itemDoc = new ItemDoc();
    itemDoc.setName("更新名称");
    UpdateResponse<ItemDoc> response = esClient.update(u -> u
                                                       .index("items")
                                                       .id("100002644680aa")
                                                       .doc(itemDoc)
                                                       .docAsUpsert(true), ItemDoc.class);
    String s = response.result().jsonValue();
    log.info("result:"+s);
}

通过docAsUpsert(true)控制,如果没有该文档则添加新文档。

4.7. 批量导入

文档地址:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/indexing-bulk.html

测试代码如下:

引包注意

@Test
void testBatchAddDocment() throws Exception {
    //取第一页10条数据
    Page<Item> page = Page.of(0, 10);
    //查询所有商品信息
    Page<Item> itemPage = itemService.page(page, new LambdaQueryWrapper<Item>());
    //获取商品集合
    List<Item> items = itemPage.getRecords();
    //拷贝属性
    List<ItemDoc> itemDocs = BeanUtils.copyList(items, ItemDoc.class);
    //批量添加请求
    BulkRequest.Builder br = new BulkRequest.Builder();
    itemDocs.forEach(itemDoc ->
                     br.operations(op -> op
                                   .index(i -> i
                                          .index("items")
                                          .id(itemDoc.getId().toString())
                                          .document(itemDoc))));
    //构建请求
    BulkRequest build = br.build();
    //批量添加
    BulkResponse bulkResponse = esClient.bulk(build);
    //遍历结果
    bulkResponse.items().forEach(item -> log.info("添加结果:{}",item.result().toString()));
    //如果有错误
    if(bulkResponse.errors()){
        log.error("批量添加失败");
        //遍历错误
        bulkResponse.items().forEach(item -> log.error("添加失败:{}",item.error().reason()));
    }
}

此时去Kibana查询,发现多了刚才新增的10条,一共12条


相关文章
|
2月前
|
安全 数据安全/隐私保护
RBAC权限模型
RBAC(基于角色的访问控制)通过角色管理权限,实现用户、角色、权限与资源的分离。其核心原则包括最小权限、职责分离与数据抽象,分为RBAC0至RBAC3四个层级,逐步支持角色继承与动态静态职责分离,提升系统安全与管理效率。
|
2月前
|
JSON Java 数据格式
String转JSON
该代码段演示了如何将字符串解析为JSON对象。通过`JSONObject.fromObject()`方法将包含中文的JSON字符串转换为JSONObject实例,并输出其标准格式化内容,适用于Java中处理JSON数据的场景。
|
2月前
|
JSON Java 数据格式
Object转JSON
该方法将Java对象转换为JSON字符串,使用JSONArray.fromObject实现序列化,并输出转换结果日志,最后返回生成的JSON字符串。适用于对象数据的JSON格式化处理。
|
2月前
|
缓存 运维 监控
一场FullGC故障排查
本文记录了一次Java应用CPU使用率异常升至104%的问题排查过程。通过分析发现,问题根源为频繁Full GC,而Full GC由内存中多个大List对象(近900MB)导致,这些对象因处理Excel样本数据时结构设计不合理而长期驻留JVM堆内存,造成空间不足。借助JProfiler分析堆快照定位到大对象后,提出“治本”与“治标”两类解决方案:一是将大数据移出JVM内存存入Redis;二是优化数据结构、及时清理无用字段以减少内存占用。最终总结了线上高CPU问题的排查思路:优先检查JVM GC情况,结合工具分析堆内存,定位代码逻辑并验证推测,强调需区分机器监控与JVM监控差异,避免误判。
 一场FullGC故障排查
|
2月前
|
人工智能 算法 机器人
AI场景面试题
本项目聚焦AI客服与智能分析,集成Ollama、MaxKB与百度云千帆大模型,实现RAG问答、健康分析等功能。采用Spring AI框架,通过API调用7B/14B级大模型,结合本地部署与云端资源,优化数据同步、模型微调与推理流程,提升响应准确率。涵盖AIGC应用、图像处理心得及DDPM等算法实践,构建高效、可扩展的智能服务体系。
|
2月前
|
安全 Java
common-lang3
避免三目运算符引发的Java自动拆装箱异常,推荐使用ObjectUtils.defaultIfNull方法替代。该方式更安全,可有效防止null值导致的运行时错误,提升代码健壮性。
|
2月前
|
SQL 关系型数据库 Java
分页
本文介绍了六种分页实现方式:MyBatis自带RowBounds内存分页、PageHelper插件分页、SQL物理分页、数组分页、拦截器分页,并对比了逻辑分页与物理分页的优劣。小数据量时逻辑分页较快,但大数据量下易内存溢出,推荐使用物理分页,效率更高更稳定。
|
2月前
|
安全 数据安全/隐私保护
SpringSecurity核心功能
SpringSecurity 是功能强大的鉴权框架,支持表单、OAuth2.0、SAML2.0、CAS 等多种认证方式,可扩展自定义逻辑。提供基于 URL、方法、SPEL 的细粒度授权,支持 RBAC 模型与动态配置,并具备防御 CSRF 等攻击的安全机制,全面保障应用安全。
|
2月前
|
XML JSON Java
引用
本代码片段展示了Java中常用的工具类与JSON处理库的导入,包括Hutool的XmlUtil用于XML解析,以及net.sf.json用于JSON对象与数组的操作,同时引入了List和Map等集合类,适用于数据解析与转换场景。
|
2月前
|
JSON fastjson Java
JSON转Object
该方法将JSON字符串转换为指定类型的Java对象,利用FastJSON库实现解析,适用于POJO类的数据反序列化,简洁高效,广泛用于Web开发中的数据处理。

热门文章

最新文章