1. 导入restClient依赖
XML<!-- es --> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.12.1</version> </dependency>
XML<!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.39</version> <!-- 使用最新稳定版 --> </dependency>
2. 了解ES核心客户端API
核心区别对比
特性 RestHighLevelClient RestClient 定位 高级客户端(封装常用操作,推荐使用) 底层HTTP客户端(更灵活,更复杂) API风格 面向对象(如 IndexRequest
,SearchRequest
)基于HTTP请求构建(如 Request
,Response
)维护状态 官方已停止维护(ES 7.15+) 仍维护(但推荐迁移到新客户端) 依赖关系 基于 RestClient
实现是 RestHighLevelClient
的底层依赖适用场景 快速开发标准功能 需要自定义请求或访问未封装API
3. 将RestHighLevelClient注册成Bean
javapackage com.example.demo.config; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ElasticSearchConfig { public static final String HOST = "127.0.0.1"; // es地址 public static final int PORT = 9200; // es端口 /** * 创建RestClient对象 * @return */ @Bean public RestHighLevelClient restClient() { return new RestHighLevelClient(RestClient.builder(new HttpHost(HOST, PORT))); } }
4. 索引库基本操作
1. 创建索引库
创建包和类用于保存索引结构:constants.HotelConstants
javapackage com.example.demo.constants; public class HotelConstants { public static final String MAPPER_TEMPLATE_USER = "{\n" + " \"settings\": {\n" + " \"number_of_shards\": 3,\n" + " \"number_of_replicas\": 1,\n" + " \"analysis\": {\n" + " \"analyzer\": {\n" + " \"ik_smart\": {\n" + " \"type\": \"custom\",\n" + " \"tokenizer\": \"ik_smart\"\n" + " },\n" + " \"ik_max_word\": {\n" + " \"type\": \"custom\",\n" + " \"tokenizer\": \"ik_max_word\"\n" + " }\n" + " }\n" + " }\n" + " },\n" + " \"mappings\": {\n" + " \"dynamic\": \"strict\",\n" + " \"properties\": {\n" + " \"id\": {\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"username\": {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_max_word\",\n" + " \"fields\": {\n" + " \"keyword\": {\n" + " \"type\": \"keyword\",\n" + " \"ignore_above\": 256\n" + " }\n" + " },\n" + " \"copy_to\": \"combined_search\"\n" + " },\n" + " \"password\": {\n" + " \"type\": \"keyword\",\n" + " \"index\": false\n" + " },\n" + " \"phone\": {\n" + " \"type\": \"keyword\",\n" + " \"ignore_above\": 20\n" + " },\n" + " \"createTime\": {\n" + " \"type\": \"date\",\n" + " \"format\": \"yyyy-MM-dd HH:mm:ss||epoch_millis\"\n" + " },\n" + " \"updateTime\": {\n" + " \"type\": \"date\",\n" + " \"format\": \"yyyy-MM-dd HH:mm:ss||epoch_millis\"\n" + " },\n" + " \"status\": {\n" + " \"type\": \"integer\",\n" + " \"null_value\": 1\n" + " },\n" + " \"balance\": {\n" + " \"type\": \"integer\",\n" + " \"null_value\": 0\n" + " },\n" + " \"combined_search\": {\n" + " \"type\": \"text\",\n" + " \"analyzer\": \"ik_smart\"\n" + " }\n" + " }\n" + " }\n" + "}"; }
java/** * 创建索引库 */ @Test public void createIndex() throws IOException { // 创建请求,指定索引库名称 CreateIndexRequest request = new CreateIndexRequest("user"); //执行与数据库相对于的映射JSON CreateIndexRequest source = request.source(HotelConstants.MAPPER_TEMPLATE_USER, XContentType.JSON); // 发送请求 //RequestOptions.DEFAULT:默认请求 client.indices().create(source, RequestOptions.DEFAULT); }
执行前先确定是否删除user索引库,客户端查询一下如果为空就是删除了
java# GET /user # DELETE /user
运行测试方法后再次查询
2. 判断索引库是否存在
java/** * 判断索引库是否存在 * @throws IOException */ @Test void testIndexExists() throws IOException { // 创建请求,指定索引库名称 GetIndexRequest request = new GetIndexRequest("user"); // 发送请求,判断索引库是否存在 boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); // 打印结果,true表示存在,false表示不存在 System.out.println(exists); }
3. 删除索引库
java/** * 删除索引库 * @throws IOException */ @Test void testDeleteIndex() throws IOException { // 创建请求,指定索引库名称 DeleteIndexRequest request = new DeleteIndexRequest("user"); // 发送请求 client.indices().delete(request, RequestOptions.DEFAULT); }
5. 文档操作
1. 新增文档
java/** * 新增文档 */ @Test public void addDoc() throws IOException, JSONException { // 读取数据库数据,发送请求,指定文档内容 User user = userService.getById(1); // 创建请求,指定索引库名称 IndexRequest indexRequest = new IndexRequest("user").id(user.getId().toString()); //转json indexRequest.source(JSON.toJSONString(user), XContentType.JSON); // 发送请求 client.index(indexRequest, RequestOptions.DEFAULT); }
2. 查询文档
java/** * 查询文档 */ @Test public void getDoc() throws IOException { // 创建请求,指定索引库名称和文档id GetRequest getRequest = new GetRequest("user", "1"); // 发送请求 GetResponse response = client.get(getRequest, RequestOptions.DEFAULT); // 打印结果 System.out.println(response.getSourceAsString()); //转user对象 User user = JSON.parseObject(response.getSourceAsString(), User.class); System.out.println(user); }
3. 更新文档
全量更新:重新添加一个包含所有的文档,ES会先将原来的删除再新增
局部更新:添加部分数据(不包括ID),ES会将相关字段值更新
java/** * 修改文档 */ @Test public void updateDoc() throws IOException { // 创建请求,指定索引库名称和文档id User user = userService.getById(1); // 修改数据 user.setUsername("admin"); // 发送请求 UpdateRequest updateRequest = new UpdateRequest("user", user.getId().toString()); // 转json updateRequest.doc(JSON.toJSONString(user), XContentType.JSON); // 发送请求 client.update(updateRequest, RequestOptions.DEFAULT); }
4. 删除文档
java/** * 删除文档 */ @Test public void deleteDoc() throws IOException { // 创建请求,删除文档id=1 DeleteRequest deleteRequest = new DeleteRequest("user").id("1"); // 发送请求 client.delete(deleteRequest, RequestOptions.DEFAULT); // 打印结果 System.out.println("删除成功"); }
5. 批处理
java/** * 批处理 */ @Test public void batchAddDoc() throws IOException { // 创建批处理请求 BulkRequest bulkRequest = new BulkRequest(); // 获取数据库数据 List<User> list = userService.list(); // 遍历集合,添加批处理请求 for (User user : list) { IndexRequest indexRequest = new IndexRequest("user").id(user.getId().toString()); indexRequest.source(JSON.toJSONString(user), XContentType.JSON); bulkRequest.add(indexRequest); } // 发送请求 client.bulk(bulkRequest, RequestOptions.DEFAULT); }
6. 全文检索(倒排索引)
GET /item/_search { "query": { "match": { "name": "莎米特SUMMIT" } } }
java/** * 搜索 * 根据名称使用倒排索引搜索 * @throws IOException */ @Test public void searchByNameSimple() throws IOException { // 1. 创建搜索请求(指定索引名) SearchRequest request = new SearchRequest("item"); // 2. 构建查询条件(name字段匹配"莎米特SUMMIT") SearchSourceBuilder source = new SearchSourceBuilder() .query(QueryBuilders.matchQuery("name", "OXLA欧莎2018")) // 基础分词查询 .from(0) // 页码(从0开始) .size(100); // 每页条数 默认10 // 3. 执行查询 SearchResponse response = client.search(request.source(source), RequestOptions.DEFAULT); // 4. 打印结果 System.out.println("找到 " + response.getHits().getTotalHits().value + " 条结果:"); for (SearchHit hit : response.getHits()) { String name = (String) hit.getSourceAsMap().get("name"); System.out.println("ID: " + hit.getId() + " | 名称: " + name); } }
7. 精确查找
java/** * 精确搜索 * @throws IOException */ @Test public void exactSearch() throws IOException { // 1. 创建搜索请求 SearchRequest request = new SearchRequest("item"); // 2. 构建精确查询条件 SearchSourceBuilder source = new SearchSourceBuilder() .query(QueryBuilders.termQuery("category", "拉杆箱")) // 使用.keyword字段 .size(5); // 3. 执行查询 SearchResponse response = client.search(request.source(source), RequestOptions.DEFAULT); // 4. 处理结果 System.out.println("精确匹配结果数: " + response.getHits().getTotalHits().value); for (SearchHit hit : response.getHits()) { System.out.println("ID: " + hit.getId() +"| 分类: "+ hit.getSourceAsMap().get("category")+ " | 名称: " + hit.getSourceAsMap().get("name")); } }
8. Bool Query(复合查询|条件查询)
布尔查询允许组合多个子查询,支持四种逻辑条件:
子句类型 说明 类比SQL 是否影响相关性评分 must
必须满足的所有条件(AND逻辑) WHERE a AND b
✅ 是 should
至少满足一个 条件(OR逻辑),可通过 minimum_should_match
调整WHERE a OR b
✅ 是 must_not
必须不满足的条件(NOT逻辑) WHERE NOT a
❌ 否 filter
必须满足的条件,但不影响评分(高性能过滤) WHERE a
❌ 否
java@Test // 标记为测试方法 public void testBoolQuerySearch() throws Exception { // ==================== 1. 构建布尔查询 ==================== BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() // MUST条件(必须满足): // - 商品名称包含"智能手机"(使用ik_smart分词器) .must(QueryBuilders.matchQuery("name", "防蓝光老花眼镜")) // - 商品状态必须为1(精确匹配) .must(QueryBuilders.termQuery("status", 1)) // SHOULD条件(至少满足一个): // - 品牌是"华为"或"小米"(使用OR逻辑) .should(QueryBuilders.matchQuery("brand", "莎米特")) .should(QueryBuilders.matchQuery("brand", "黛丝")) .minimumShouldMatch(1) // 至少满足1个should条件 // MUST_NOT条件(必须不满足): // - 价格不能高于1000元 .mustNot(QueryBuilders.rangeQuery("price").gt(1000)) // - 不能是广告商品 .mustNot(QueryBuilders.termQuery("isAD", true)) // FILTER条件(必须满足,但不影响评分): // - 库存必须大于0 .filter(QueryBuilders.rangeQuery("stock").gt(0)); // - 创建时间在2024年内(日期范围查询) /* .filter(QueryBuilders.rangeQuery("create_time") .gte("2018-1-1 00:00:00") // 大于等于起始时间 .lte("2024-12-31 23:59:59")) // 小于等于结束时间 // - 分类必须是"电子产品"或"数码配件"(精确匹配) .filter(QueryBuilders.termsQuery("category.keyword", "老花镜", "数码配件"));*/ // ==================== 2. 配置查询请求 ==================== SearchSourceBuilder sourceBuilder = new SearchSourceBuilder() .query(boolQuery) // 设置查询条件 .from(0) // 分页起始位置(第1页) .size(10) // 每页返回10条 .sort("price", SortOrder.ASC) // 按价格升序 .sort("_score", SortOrder.DESC); // 按相关性降序 // ==================== 3. 执行查询 ==================== SearchResponse response = client.search( new SearchRequest("item") // 指定索引名 .source(sourceBuilder), // 设置查询参数 RequestOptions.DEFAULT // 默认请求选项 ); // ==================== 4. 处理结果 ==================== System.out.println("命中数量: " + response.getHits().getTotalHits().value); // 遍历结果并打印关键信息 Arrays.stream(response.getHits().getHits()) .forEach(hit -> { System.out.println("\n========== 商品信息 =========="); System.out.println("ID: " + hit.getId()); System.out.println("名称: " + hit.getSourceAsMap().get("name")); System.out.println("价格: ¥" + hit.getSourceAsMap().get("price")); System.out.println("品牌: " + hit.getSourceAsMap().get("brand")); System.out.println("库存: " + hit.getSourceAsMap().get("stock")); });
9. 排序
java/** * 排序示例 * @throws Exception */ @Test public void testSortingExamples() throws Exception { // ==================== 1. 基础排序 ==================== // 示例1: 按价格升序排序 SearchSourceBuilder priceAscBuilder = new SearchSourceBuilder() .query(QueryBuilders.matchAllQuery()) .sort("price", SortOrder.ASC) // 价格从低到高 .size(5); // 示例2: 按创建时间降序排序(新品优先) SearchSourceBuilder newFirstBuilder = new SearchSourceBuilder() .query(QueryBuilders.matchAllQuery()) .sort("createTime", SortOrder.DESC) // 最新创建的商品在前 .size(5); // ==================== 2. 多字段排序 ==================== // 示例3: 先按品牌字母序,再按价格降序 SearchSourceBuilder multiFieldBuilder = new SearchSourceBuilder() .query(QueryBuilders.matchAllQuery()) .sort("brand.keyword", SortOrder.ASC) // 品牌名A-Z排序 .sort("price", SortOrder.DESC) // 同品牌中价格高的在前 .size(5); // ==================== 3. 特殊排序 ==================== // 示例4: 按库存升序(库存少的优先) SearchSourceBuilder stockSortBuilder = new SearchSourceBuilder() .query(QueryBuilders.matchAllQuery()) .sort("stock", SortOrder.ASC) .size(5); // 示例5: 按销量降序(热销商品在前) SearchSourceBuilder soldSortBuilder = new SearchSourceBuilder() .query(QueryBuilders.matchAllQuery()) .sort("sold", SortOrder.DESC) .size(5); // ==================== 4. 执行并打印结果 ==================== // 执行价格排序查询 System.out.println("======== 按价格升序排序 ========"); SearchResponse priceResponse = client.search( new SearchRequest("item").source(priceAscBuilder), RequestOptions.DEFAULT ); printResults(priceResponse); // 执行新品排序查询 System.out.println("\n======== 按新品排序(创建时间降序) ========"); SearchResponse newResponse = client.search( new SearchRequest("item").source(newFirstBuilder), RequestOptions.DEFAULT ); printResults(newResponse); // 执行多字段排序查询 System.out.println("\n======== 多字段排序(品牌A-Z,价格高-低) ========"); SearchResponse multiResponse = client.search( new SearchRequest("item").source(multiFieldBuilder), RequestOptions.DEFAULT ); printResults(multiResponse); } // 打印结果的辅助方法 private void printResults(SearchResponse response) { Arrays.stream(response.getHits().getHits()) .forEach(hit -> { Map<String, Object> source = hit.getSourceAsMap(); System.out.printf( "ID: %s | 名称: %-20s | 价格: %-6s | 品牌: %-8s | 库存: %-4s | 创建时间: %s\n", hit.getId(), source.get("name"), source.get("price"), source.get("brand"), source.get("stock"), source.get("createTime") ); }); }
10. 分页
四种分页场景实现:
testBasicPagination()
: 首页商品列表,按销量+上新时间排序
testCategoryPagination()
: 分类页,带精确分类筛选和价格排序
testSearchPagination()
: 搜索页,支持关键词高亮显示
testDeepPagination()
: 深度分页,使用search_after技术
java/** * 基础分页查询 - 首页商品列表 * 特点:按综合排序(销量+上新) */ @Test public void testBasicPagination() throws Exception { int page = 1; // 当前页码 int size = 10; // 每页数量 SearchSourceBuilder builder = new SearchSourceBuilder() .query(QueryBuilders.termQuery("status", 1)) // 只查询上架商品 .from((page - 1) * size) .size(size) .sort("sold", SortOrder.DESC) // 按销量降序 .sort("create_time", SortOrder.DESC) // 再按创建时间降序 .fetchSource(new String[]{"id", "name", "price", "image", "sold"}, null); // 只返回必要字段 SearchResponse response = client.search( new SearchRequest("item").source(builder), RequestOptions.DEFAULT ); printPageResult(response, page, size); } /** * 分类页分页 - 带筛选条件 * 特点:分类筛选+多维度排序 */ @Test public void testCategoryPagination() throws Exception { int page = 1; int size = 15; String category = "老花镜"; BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("category.keyword", category)) // 精确分类匹配 .must(QueryBuilders.termQuery("status", 1)) // 上架商品 .filter(QueryBuilders.rangeQuery("stock").gt(0)); // 有库存 SearchSourceBuilder builder = new SearchSourceBuilder() .query(boolQuery) .from((page - 1) * size) .size(size) .sort("price", SortOrder.ASC) // 按价格升序 .sort("sold", SortOrder.DESC) // 再按销量降序 .fetchSource(new String[]{"id", "name", "price", "image", "brand"}, null); SearchResponse response = client.search( new SearchRequest("item").source(builder), RequestOptions.DEFAULT ); printPageResult(response, page, size); } /** * 搜索分页 - 带关键词和高亮 * 特点:关键词搜索+结果高亮 */ @Test public void testSearchPagination() throws Exception { int page = 1; int size = 10; String keyword = "防蓝光"; SearchSourceBuilder builder = new SearchSourceBuilder() .query(QueryBuilders.boolQuery() .must(QueryBuilders.multiMatchQuery(keyword, "name", "spec")) .must(QueryBuilders.termQuery("status", 1)) ) .from((page - 1) * size) .size(size) .sort("_score", SortOrder.DESC) // 按相关性排序 .sort("sold", SortOrder.DESC) // 再按销量排序 .highlighter(new HighlightBuilder() .field("name") .field("spec") .preTags("<em>") .postTags("</em>")) .fetchSource(new String[]{"id", "name", "price", "image"}, null); SearchResponse response = client.search( new SearchRequest("item").source(builder), RequestOptions.DEFAULT ); printSearchResult(response, page, size); } /** * 深度分页 - 使用search_after * 特点:适合无限滚动加载 */ @Test public void testDeepPagination() throws Exception { // 模拟上一页最后一条的排序值 Object[] lastSortValues = new Object[]{50000, "2023-05-01T10:00:00.000Z"}; int size = 10; SearchSourceBuilder builder = new SearchSourceBuilder() .query(QueryBuilders.termQuery("status", 1)) .size(size) .sort("sold", SortOrder.DESC) // 必须与lastSortValues中的顺序一致 .sort("create_time", SortOrder.DESC) .searchAfter(lastSortValues) .fetchSource(new String[]{"id", "name", "price", "sold"}, null); SearchResponse response = client.search( new SearchRequest("item").source(builder), RequestOptions.DEFAULT ); printPageResult(response, -1, size); // -1表示未知页码 } // ==================== 辅助方法 ==================== /** * 打印分页结果 */ private void printPageResult(SearchResponse response, int currentPage, int pageSize) { long totalHits = response.getHits().getTotalHits().value; int totalPages = (int) Math.ceil((double) totalHits / pageSize); if (currentPage > 0) { System.out.printf("\n=== 第 %d 页 (共 %d 页,每页 %d 条) ===\n", currentPage, totalPages, pageSize); } else { System.out.println("\n=== 深度分页结果 ==="); } Arrays.stream(response.getHits().getHits()) .forEach(hit -> { Map<String, Object> source = hit.getSourceAsMap(); System.out.printf("ID: %s | 商品: %-25s | 价格: %-6s | 销量: %-5s | 图片: %s\n", hit.getId(), source.get("name"), source.get("price"), source.get("sold"), source.get("image")); }); } /** * 打印搜索结果(带高亮) */ private void printSearchResult(SearchResponse response, int currentPage, int pageSize) { System.out.printf("\n=== 搜索第 %d 页 ===\n", currentPage); Arrays.stream(response.getHits().getHits()) .forEach(hit -> { Map<String, Object> source = hit.getSourceAsMap(); Map<String, HighlightField> highlights = hit.getHighlightFields(); // 获取高亮名称 String nameHighlight = highlights.containsKey("name") ? highlights.get("name").fragments()[0].string() : source.get("name").toString(); // 获取高亮规格 String specHighlight = highlights.containsKey("spec") ? highlights.get("spec").fragments()[0].string() : source.getOrDefault("spec", "").toString(); System.out.printf("ID: %s | 商品: %-25s | 价格: %-6s | 规格: %s\n", hit.getId(), nameHighlight, source.get("price"), specHighlight); }); }
ES基本操作(Java API)
小汤猿人类2025-04-20 23:28
相关推荐
ghost1436 分钟前
C#学习第17天:序列化和反序列化xxjiaz14 分钟前
二分查找-LeetCodenofaluse37 分钟前
JavaWeb开发——文件上传難釋懷41 分钟前
bash的特性-bash中的引号爱的叹息1 小时前
【java实现+4种变体完整例子】排序算法中【插入排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格Hello eveybody2 小时前
C++按位与(&)、按位或(|)和按位异或(^)爱的叹息2 小时前
【java实现+4种变体完整例子】排序算法中【快速排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格6v6-博客2 小时前
2024年网站开发语言选择指南:PHP/Java/Node.js/Python如何选型?Baoing_2 小时前
Next.js项目生成sitemap.xml站点地图Ac157ol2 小时前
2025年最新版 Git和Github的绑定方法,以及通过Git提交文件至Github的具体流程(详细版)