> 技术文档 > Elasticsearch 讲解及 Java 应用实战:从入门到落地

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 为例):


co.elastic.clients

elasticsearch-java

8.10.4

com.fasterxml.jackson.core

jackson-databind

2.15.2

org.apache.httpcomponents.client5

httpclient5

5.3

(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 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 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 response = client.search(request, Product.class);

// 处理结果

List products = new ArrayList();

for (Hit 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 {

private long total; // 总条数

private List data; // 数据列表

public SearchResult(long total, List 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 应用中遇到过特殊场景(如大数据量同步、复杂聚合分析),欢迎在评论区分享你的解决方案!