作为一名后端开发,我曾被 “海量数据检索” 这个需求难住 —— 普通数据库查 10 万条数据要等好几秒,用户早就没耐心了。后来接触到 Elasticsearch(简称 ES),才发现它能把检索速度提升到毫秒级,而 REST API 就是和 ES 打交道的 “万能钥匙”。
不过刚学的时候,我也踩了不少坑:搞不清 “索引” 和 “文档” 的区别,写个 API 请求总报格式错误,Java 集成时又因为版本不兼容卡了半天。今天就把这些经验整理成一篇指南,从最基础的概念讲起,再到实际操作和 Java 落地,尽量用通俗的话讲明白,帮大家少走弯路~
一、先搞懂 3 个核心概念,不然 API 根本用不明白
很多人刚学 ES 时,一上来就查 API 文档,结果越看越懵 —— 其实是没搞懂 ES 的 “数据逻辑”。咱们可以把它和熟悉的 MySQL 对比,一下子就清楚了:
ES 里的概念对应 MySQL 里的啥简单理解
索引(Index)数据库(Database)存一类数据的 “大容器”,比如 “商品库”“订单库”
映射(Mapping)表结构(Table Schema)规定数据的 “格式”,比如哪个字段是文字、哪个是数字、能不能搜索
文档(Document)行数据(Row)具体的一条数据,用 JSON 格式存,比如 “华为 Mate 60 的商品信息”
举个例子:如果要做商品检索,第一步得建个 “商品索引”(相当于建了个商品数据库),第二步定义 “映射”(规定商品 ID 是数字、商品名是可搜索的文字),第三步才往里面加 “文档”(一条条商品数据)。
搞懂这个逻辑,后面用 API 就不会 “无的放矢” 了~
二、ES REST API 实操:用 curl/Postman 轻松上手
ES 的 REST API 特别好理解,就是 “用 HTTP 方法发请求”—— 比如查数据用 GET,加数据用 POST,改数据用 PUT,删数据用 DELETE。下面用 curl 命令演示常用操作,Postman 操作也一样,复制 URL 和请求体就行。
1. 先搞定 “索引”:建库、查结构、删库
(1)创建索引(顺便定义映射)
需求:建一个 “商品索引”,存商品 ID、名称、价格、创建时间,还要支持中文搜索。
PUT http://localhost:9200/product_index
{
"mappings": {
"properties": {
"product_id": { "type": "integer" }, # 商品ID,数字类型
"product_name": {
"type": "text",
"analyzer": "ik_max_word" # 中文分词器,要先装IK插件哦
},
"price": { "type": "float" }, # 价格,浮点型
"create_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss" # 日期格式
}
}
}
}
小贴士:如果用的是 ES 7.x 及以上版本,别在 mappings 里加 “_doc” 这种 “类型名”,不然会报错 —— 新版本已经取消这个概念了。
(2)查索引结构
建完之后,想确认一下格式对不对,用 GET 请求:
# 查单个索引的结构
GET http://localhost:9200/product_index/_mapping
# 查所有索引列表(加?v显示表头,看得更清楚)
GET http://localhost:9200/_cat/indices?v
(3)删除索引(谨慎!删了就找不回来了)
如果建错了要删除,用 DELETE 请求:
DELETE http://localhost:9200/product_index
2. 再操作 “文档”:增删改查数据
(1)新增文档(加数据)
有两种方式:指定 ID,或者让 ES 自动生成 ID。
# 1. 指定ID(适合知道唯一标识的场景,比如用商品ID当文档ID)
PUT http://localhost:9200/product_index/_doc/1
{
"product_id": 1001,
"product_name": "华为Mate 60 Pro 5G手机",
"price": 6999.0,
"create_time": "2024-01-15 10:30:00"
}
# 2. 不指定ID(ES自动生成随机ID,适合批量加数据)
POST http://localhost:9200/product_index/_doc
{
"product_id": 1002,
"product_name": "苹果iPhone 15 Pro",
"price": 9999.0,
"create_time": "2024-02-20 14:15:00"
}
(2)查询文档(查数据)
按 ID 查:最快的方式,直接定位到某条数据。
GET http://localhost:9200/product_index/_doc/1
条件查:比如搜 “手机” 关键词,还要按价格降序排,每页看 2 条。
GET http://localhost:9200/product_index/_search
{
"query": {
"match": { "product_name": "手机" } # 分词匹配,会找到所有带“手机”的商品
},
"from": 0, # 从第0条开始(就是第1页)
"size": 2, # 每页2条
"sort": [{ "price": "desc" }] # 按价格降序
}
(3)更新文档(改数据)
有两种方式:全量更(覆盖整条数据)和局部更(只改某字段)。
# 1. 全量更新(要传所有字段,不然没传的会丢)
PUT http://localhost:9200/product_index/_doc/1
{
"product_id": 1001,
"product_name": "华为Mate 60 Pro 5G手机(12GB+512GB)", # 只改了名称
"price": 6999.0, # 没改的字段也要传
"create_time": "2024-01-15 10:30:00"
}
# 2. 局部更新(只传要改的字段,效率高)
POST http://localhost:9200/product_index/_update/1
{
"doc": { "price": 6799.0 } # 只改价格
}
(4)删除文档(删数据)
按 ID 删,很简单:
DELETE http://localhost:9200/product_index/_doc/1
3. 批量操作:用 Bulk API 省时间
如果要加几十上百条数据,一条一条传太慢了,用 Bulk API 一次搞定,效率能提 10 倍以上。
格式有个小要求:每两行一组,第一行说要做啥,第二行是数据,最后还要空一行。
示例:批量加 2 个商品,再删 1 个商品。
POST http://localhost:9200/_bulk
{"index":{"_index":"product_index","_id":"3"}} # 操作:新增,索引:product_index,ID=3
{"product_id":1003,"product_name":"小米14","price":4299.0,"create_time":"2024-03-01 09:00:00"}
{"index":{"_index":"product_index","_id":"4"}}
{"product_id":1004,"product_name":"OPPO Find X7","price":4499.0,"create_time":"2024-03-10 11:00:00"}
{"delete":{"_index":"product_index","_id":"2"}} # 操作:删除,ID=2
踩坑提醒:Bulk 请求体不能格式化(别换行缩进),不然会报 “JSON 格式错误”,我第一次用就因为这个卡了半小时…
三、Java 实战:把 ES 集成到项目里
光用 curl 测试还不够,实际项目要在 Java 里调用 ES API。官方推荐用 “Elasticsearch Rest High Level Client”,封装得很好,不用自己写 HTTP 请求。
1. 第一步:加依赖(Maven)
关键是客户端版本要和 ES 服务器版本一致,比如服务器是 7.17.0,客户端也得是 7.17.0,不然会不兼容。
<!-- ES高level客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.0</version>
</dependency>
<!-- ES核心包 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.17.0</version>
</dependency>
<!-- Jackson:转JSON用 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.5</version>
</dependency>
2. 第二步:建客户端(单例模式)
客户端不用每次都新建,用单例模式建一个就行,不然连接太多会出问题。
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class EsClientUtil {
// 单例客户端
private static RestHighLevelClient client;
// 初始化客户端
static {
client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http") // 改成你的ES地址
)
);
}
// 获取客户端
public RestHighLevelClient getClient() {
return client;
}
// 项目关闭时关闭客户端
public void closeClient() throws IOException {
if (client != null) {
client.close();
}
}
}
3. 第三步:实现商品检索功能
需求:根据商品名关键词和价格区间搜商品,还要分页。
(1)先建个商品实体类
import lombok.Data;
import java.util.Date;
@Data // 用Lombok省点代码,不想用也可以手动写getter/setter
public class Product {
private Integer productId;
private String productName;
private Float price;
private Date createTime;
}
(2)写检索服务
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
public class ProductSearchService {
@Autowired
private EsClientUtil esClientUtil;
private final ObjectMapper objectMapper = new ObjectMapper();
private final String INDEX_NAME = "product_index"; // 索引名
// 商品检索方法
public List<Product> searchProducts(String keyword, Float minPrice, Float maxPrice,
Integer pageNum, Integer pageSize) throws IOException {
// 1. 建搜索请求
SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
// 2. 拼查询条件
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词模糊匹配(允许1个字符输错,比如“华为”输成“华维”也能搜到)
if (keyword != null && !keyword.isEmpty()) {
boolQuery.must(QueryBuilders.matchQuery("product_name", keyword)
.fuzziness(Fuzziness.ONE));
}
// 价格区间过滤
if (minPrice != null && maxPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price")
.gte(minPrice) // 大于等于
.lte(maxPrice)); // 小于等于
} else if (minPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice));
} else if (maxPrice != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(maxPrice));
}
// 3. 设分页和排序
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(boolQuery);
sourceBuilder.from((pageNum - 1) * pageSize); // 起始位置
sourceBuilder.size(pageSize); // 每页条数
sourceBuilder.sort("create_time", SortOrder.DESC); // 按创建时间降序
// 4. 执行搜索
searchRequest.source(sourceBuilder);
RestHighLevelClient client = esClientUtil.getClient();
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 5. 解析结果,转成Product列表
List<Product> productList = new ArrayList<>();
response.getHits().forEach(hit -> {
try {
Product product = objectMapper.readValue(hit.getSourceAsString(), Product.class);
productList.add(product);
} catch (IOException e) {
e.printStackTrace();
}
});
return productList;
}
}
(3)写个接口测试
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
@RestController
public class ProductSearchController {
@Autowired
private ProductSearchService productSearchService;
@GetMapping("/search/products")
public List<Product> searchProducts(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Float minPrice,
@RequestParam(required = false) Float maxPrice,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) throws IOException {
return productSearchService.searchProducts(keyword, minPrice, maxPrice, pageNum, pageSize);
}
}
启动项目后,访问这个地址就能搜商品了:
http://localhost:8080/search/products?keyword=手机&minPrice=4000&maxPrice=8000&pageNum=1&pageSize=5
四、避坑指南:我踩过的坑,你别再踩了
连接失败 “Connection refused”
先检查 ES 服务器开没开,地址和端口对不对,Linux 的话还要看看 9200 端口有没有开放(用firewall-cmd --zone=public --add-port=9200/tcp --permanent开放)。
字段类型不匹配报错
比如映射里定义 product_name 是 text,结果传了个数字进去,肯定报错。新增数据前,先对照映射检查字段类型。
Bulk API 报 JSON 错误
记住两点:每两行一组,最后空一行;请求体别格式化,紧凑排列。
Java 客户端版本不兼容
客户端版本和 ES 服务器版本必须一致,别图省事随便写个版本号。
五、最后总结
ES REST API 其实没那么难,关键是先搞懂 “索引 - 映射 - 文档” 的概念,再通过实际操作熟悉请求格式,最后用 Java 客户端集成到项目里。
我刚开始学的时候也觉得复杂,但多试几次就会发现规律 —— 比如所有 API 都围绕 “操作索引” 和 “操作文档” 展开,Java 代码里的查询条件也都是拼出来的。
如果大家在实操中遇到其他问题,欢迎在评论区交流,咱们一起解决~