Elasticsearch 讲解及 Java 应用实战:从入门到落地

在数据量爆炸的今天,传统数据库的查询能力越来越难以满足复杂的检索需求。比如电商平台的商品搜索,需要支持关键词模糊匹配、多条件筛选、热门度排序等功能,这时候 Elasticsearch(简称 ES)就成了最佳选择。作为一款分布式全文搜索引擎,ES 以其强大的检索能力、灵活的扩展性被广泛应用。本文将从 ES 核心概念讲起,结合 Java 实战案例,带你快速掌握 ES 的应用技巧。

一、Elasticsearch 核心概念与优势

在使用 ES 前,我们需要先理清它的核心概念 ------ 很多人会把 ES 和数据库类比,这个思路其实很实用。

1.1 ES 与关系型数据库的概念对应

如果把 ES 比作数据库,两者的核心概念可以这样对应:

|---------------|---------------|-------------------------------------------|
| 关系型数据库 | Elasticsearch | 说明 |
| Database(数据库) | Index(索引) | 一个索引对应一类数据集合,比如 "商品索引""用户索引" |
| Table(表) | Type(类型) | 早期 ES 用于区分索引内的不同数据类型,7.x 后已废弃,一个索引只对应一种类型 |
| Row(行) | Document(文档) | 索引中的一条数据,以 JSON 格式存储 |
| Column(列) | Field(字段) | 文档中的一个属性,比如商品的 "名称""价格" |
| Schema(表结构) | Mapping(映射) | 定义文档中字段的类型、分词器等规则 |

1.2 ES 的核心优势

相比传统数据库和其他搜索引擎,ES 的核心竞争力体现在这几点:

  • 全文检索能力:支持关键词分词、模糊匹配、同义词扩展等,比如搜索 "手机" 时能匹配 "智能手机""移动电话"。
  • 分布式架构:天然支持分片和副本,数据自动分片存储,副本保证高可用,可轻松扩展至海量数据。
  • 近实时搜索:数据写入后秒级可查,兼顾实时性和性能。
  • 灵活的聚合分析:支持统计、排序、分组等复杂分析,比如按 "商品分类" 统计销量 Top10。

二、Elasticsearch 核心操作入门

要使用 ES,首先要掌握其基础操作。ES 提供 RESTful API 接口,可通过 HTTP 请求直接操作,也可通过客户端工具(如 Kibana 的 Dev Tools)执行。

2.1 索引相关操作

(1)创建索引及映射

创建一个 "商品索引",并定义字段映射(类似数据库建表时定义字段类型):

复制代码

# 创建商品索引

PUT /product_index

{

"mappings": {

"properties": {

"id": { "type": "keyword" }, // 商品ID,精确匹配,不分词

"name": {

"type": "text",

"analyzer": "ik_max_word", // 使用IK分词器(中文分词必备)

"fields": {

"keyword": { "type": "keyword" } // 用于精确查询或排序

}

},

"price": { "type": "double" }, // 价格

"category": { "type": "keyword" }, // 分类,精确匹配

"createTime": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" } // 创建时间

}

}

}

这里有两个关键注意点:

  • text 与 keyword:text类型用于全文检索(会分词),keyword用于精确匹配(不分词),同一个字段可通过fields同时支持两种类型。
  • 中文分词:必须使用 IK 分词器(需提前安装),ik_max_word会将文本拆分为最细粒度(如 "华为手机" 拆为 "华为""手机"),ik_smart为粗粒度拆分。
(2)查看索引映射
复制代码

GET /product_index/_mapping

(3)删除索引
复制代码

DELETE /product_index

2.2 文档相关操作

文档是 ES 中最小的数据单元,所有操作围绕文档展开。

(1)新增文档
复制代码

# 新增文档(指定ID)

PUT /product_index/_doc/1001

{

"id": "1001",

"name": "华为Mate 60 Pro 智能手机",

"price": 6999.00,

"category": "手机",

"createTime": "2024-01-15 09:30:00"

}

# 新增文档(自动生成ID)

POST /product_index/_doc

{

"id": "1002",

"name": "苹果iPhone 15 手机",

"price": 7999.00,

"category": "手机",

"createTime": "2024-01-20 14:20:00"

}

(2)查询文档

ES 的查询能力非常强大,这里列举几种常用场景:

  • 精确查询(根据分类查询手机商品):
复制代码

GET /product_index/_search

{

"query": {

"term": {

"category": { "value": "手机" }

}

}

}

  • 全文检索(搜索包含 "华为" 的商品):
复制代码

GET /product_index/_search

{

"query": {

"match": {

"name": "华为"

}

}

}

  • 组合条件查询(价格在 5000-8000,且分类为手机):
复制代码

GET /product_index/_search

{

"query": {

"bool": {

"must": [

{ "term": { "category": "手机" } },

{ "range": { "price": { "gte": 5000, "lte": 8000 } } }

]

}

},

"sort": [{"createTime": "desc"}], // 按创建时间倒序

"from": 0, "size": 10 // 分页(从第0条开始,取10条)

}

(3)更新与删除文档
  • 更新文档(全量更新或局部更新):
复制代码

# 局部更新(只更新价格)

POST /product_index/_update/1001

{

"doc": {

"price": 6799.00

}

}

  • 删除文档:
复制代码

DELETE /product_index/_doc/1001

三、Java 操作 Elasticsearch 实战

实际开发中,我们很少直接调用 REST API,而是通过官方客户端工具操作。ES 官方推荐的 Java 客户端是 Elasticsearch Java Client(7.x 后替代了旧的 Transport Client)。

3.1 环境准备

(1)引入依赖

在 Maven 项目的pom.xml中添加依赖(版本需与 ES 服务器一致,这里以 8.10.4 为例):

复制代码

<dependencies>

<!-- Elasticsearch Java Client -->

<dependency>

<groupId>co.elastic.clients</groupId>

<artifactId>elasticsearch-java</artifactId>

<version>8.10.4</version>

</dependency>

<!-- 依赖Jackson处理JSON -->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.15.2</version>

</dependency>

<!-- 连接池 -->

<dependency>

<groupId>org.apache.httpcomponents.client5</groupId>

<artifactId>httpclient5</artifactId>

<version>5.3</version>

</dependency>

</dependencies>

(2)初始化客户端

创建 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;

public class EsClient {

// 单例客户端

private static ElasticsearchClient client;

static {

// 创建REST客户端(连接ES服务器,可配置多个节点)

RestClient restClient = RestClient.builder(

new HttpHost("localhost", 9200, "http")

).build();

// 创建传输层

ElasticsearchTransport transport = new RestClientTransport(

restClient, new JacksonJsonpMapper()

);

// 创建客户端

client = new ElasticsearchClient(transport);

}

public static ElasticsearchClient getClient() {

return client;

}

}

注意:ES 客户端版本必须与服务器版本保持一致,否则可能出现兼容性问题。

3.2 核心操作实战:商品搜索功能

以 "电商商品搜索" 为例,实现文档新增、条件查询、分页排序等核心功能。

(1)定义商品实体类
复制代码

import com.fasterxml.jackson.annotation.JsonFormat;

import lombok.Data;

import java.util.Date;

@Data

public class Product {

private String id;

private String name;

private Double price;

private String category;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

private Date createTime;

}

(2)新增商品文档
复制代码

import co.elastic.clients.elasticsearch.ElasticsearchClient;

import co.elastic.clients.elasticsearch.core.IndexRequest;

import co.elastic.clients.elasticsearch.core.IndexResponse;

import java.io.IOException;

public class ProductService {

private final ElasticsearchClient client = EsClient.getClient();

// 新增商品到ES

public void addProduct(Product product) throws IOException {

// 构建索引请求(指定索引名、文档ID和数据)

IndexRequest<Product> request = IndexRequest.of(b -> b

.index("product_index") // 索引名

.id(product.getId()) // 文档ID(可选,不指定则自动生成)

.document(product) // 文档数据

);

// 执行请求

IndexResponse response = client.index(request);

System.out.println("新增结果:" + response.result());

}

}

(3)多条件查询商品

实现一个带条件、分页、排序的商品搜索功能:

复制代码

import co.elastic.clients.elasticsearch.core.SearchRequest;

import co.elastic.clients.elasticsearch.core.SearchResponse;

import co.elastic.clients.elasticsearch.core.search.Hit;

import co.elastic.clients.elasticsearch.core.search.TotalHits;

import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

public class ProductService {

// ... 其他代码省略

/**

* 搜索商品

* @param keyword 关键词(搜索商品名称)

* @param category 分类(可选)

* @param minPrice 最低价格(可选)

* @param maxPrice 最高价格(可选)

* @param page 页码(从1开始)

* @param size 每页条数

* @return 商品列表及总条数

*/

public SearchResult<Product> searchProducts(String keyword, String category,

Double minPrice, Double maxPrice,

int page, int size) throws IOException {

// 构建查询条件

SearchRequest request = SearchRequest.of(b -> b

.index("product_index") // 指定索引

.query(q -> q

.bool(bool -> {

// 拼接查询条件

if (keyword != null && !keyword.isEmpty()) {

// 全文检索商品名称

bool.must(m -> m.match(t -> t.field("name").query(keyword)));

}

if (category != null && !category.isEmpty()) {

// 精确匹配分类

bool.filter(f -> f.term(t -> t.field("category").value(category)));

}

if (minPrice != null || maxPrice != null) {

// 价格范围过滤

bool.filter(f -> f.range(r -> {

if (minPrice != null) r.gte(minPrice);

if (maxPrice != null) r.lte(maxPrice);

return r.field("price");

}));

}

return bool;

})

)

.sort(s -> s.field(f -> f.field("createTime").order("desc"))) // 按创建时间倒序

.from((page - 1) * size) // 起始位置(分页)

.size(size) // 每页条数

);

// 执行查询

SearchResponse<Product> response = client.search(request, Product.class);

// 处理结果

List<Product> products = new ArrayList<>();

for (Hit<Product> hit : response.hits().hits()) {

products.add(hit.source()); // 获取文档数据

}

// 总条数

TotalHits totalHits = response.hits().total();

long total = 0;

if (totalHits != null && totalHits.relation() == TotalHitsRelation.Eq) {

total = totalHits.value();

}

return new SearchResult<>(total, products);

}

// 定义结果封装类

@Data

public static class SearchResult<T> {

private long total; // 总条数

private List<T> data; // 数据列表

public SearchResult(long total, List<T> data) {

this.total = total;

this.data = data;

}

}

}

这个方法包含了 ES 查询的核心场景:

  • 多条件组合:用bool查询拼接 "必须满足(must)" 和 "过滤(filter)" 条件;
  • 全文检索:match查询用于商品名称的关键词搜索;
  • 精确匹配与范围过滤:term查询匹配分类,range查询过滤价格;
  • 分页与排序:通过from、size实现分页,sort指定排序规则。

3.3 性能优化技巧

在实际应用中,ES 的性能优化不可忽视,尤其是数据量大或查询频繁的场景:

  1. 合理设计映射:
    • 不需要检索的字段设置index: false(如商品详情的大文本);
    • 精确匹配字段用keyword类型,避免text类型的不必要分词。
  1. 优化查询语句:
    • 用filter代替must(filter不计算评分,且可缓存结果);
    • 避免wildcard前缀匹配(如name:*手机),会导致全表扫描;
    • 分页查询时,深分页(如from:10000)改用search_after。
  1. 索引分片优化:
    • 初始分片数设置为 "节点数 ×1~3"(如 3 个节点可设 5 个分片);
    • 分片大小控制在 20GB~50GB 之间,过大影响查询性能。
  1. 批量操作:
复制代码

// 批量新增示例

BulkRequest.Builder bulk = new BulkRequest.Builder();

for (Product product : productList) {

bulk.operations(op -> op

.index(idx -> idx

.index("product_index")

.id(product.getId())

.document(product)

)

);

}

client.bulk(bulk.build());

    • 批量新增 / 更新用BulkRequest,减少网络请求次数;

四、常见问题与解决方案

在 ES 应用中,新手容易遇到一些 "坑",这里总结几个高频问题:

  1. 中文分词效果差:
    • 原因:未安装 IK 分词器,默认分词器会把中文拆成单字;
    • 解决:在 ES 插件目录安装 IK 分词器(版本与 ES 一致),并在映射中指定analyzer: "ik_max_word"。
  1. 查询结果与预期不符:
    • 检查字段类型:如果用term查询text类型字段,可能因分词导致匹配失败(需用match查询);
    • 检查分词结果:通过GET /product_index/_analyze测试字段分词效果。
  1. 写入 / 查询性能慢:
    • 写入慢:检查分片副本数(初期可设 1 个副本)、是否开启批量写入;
    • 查询慢:查看是否命中索引(通过explain分析查询计划)、是否需要增加节点或优化分片。

五、总结

Elasticsearch 是一款强大的搜索引擎,其核心价值在于全文检索 和分布式扩展能力。本文从基础概念出发,通过 "理论 + 实战" 的方式讲解了 ES 的核心操作,并结合 Java 案例实现了商品搜索功能。

要熟练掌握 ES,关键在于:

  1. 理解索引、文档、映射等核心概念,明确text与keyword的区别;
  1. 掌握bool组合查询、范围查询、聚合分析等核心语法;
  1. 结合实际场景优化映射设计和查询语句,避免性能问题。

如果你在 ES 应用中遇到过特殊场景(如大数据量同步、复杂聚合分析),欢迎在评论区分享你的解决方案!

相关推荐
开开心心就好11 分钟前
本地文件搜索工具,双击Ctrl就能呼出搜索
java·服务器·前端·支持向量机·ffmpeg·音视频·模拟退火算法
独隅15 分钟前
Claude 高效使用指南深度评测
java·开发语言·claude·claude code
ByteCoder.23 分钟前
班前十分钟的清单,按体检引导单来写
java·服务器·前端·助播虾
海的透彻24 分钟前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
疯狂学习GIS29 分钟前
将谷歌地球引擎账号升级为Contributor与Partner层级的方法
java·数据库·人工智能
GG.WinGel40 分钟前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
泡泡糖的中文规格书1 小时前
HDLbits 2.2 学习笔记 · 多路复用器
java·开发语言
John_Hui1 小时前
第 3 篇:配置 JDK 17 环境
java·开发语言·ubuntu
秋天的怀念_David1 小时前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
2601_965969061 小时前
AI求职能力证明指南:认证证书、项目作品与实际工作成果
大数据