SpringBoot 集成ElasticSearch(二)分页查询

本文介绍SpringBoot结合ElasticSearch实现分页查询的两种方法:一种使用原生API,另一种通过Spring Data Elasticsearch封装。包括代码示例及查询条件如模糊匹配、精确匹配等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringBoot 集成ElasticSearch 之分页查询

       SpringBoot 集成ElasticSearch 的代码,我在上一篇博客中已经写过,这边就不重复了。主要记录下service层实现类中分页查询的方法
       实体类、配置信息以及新增、更新的代码在上一篇博客中,地址:

SpringBoot 集成ElasticSearch

       我在网上找了几种方法,我这里暂时只记录其中两种查询方式,第一种是几乎原生的API,第二种是经过spring二次封装的;两种方式都能查询成功

注 :两种方式的 page 都是从 0 开始,我这里是做了减一处理的

第一种

导包

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;

代码

@Qualifier("client")
@Autowired
private RestHighLevelClient restHighLevelClient;

@Override
public Map<String,Object> query(Map<String,Object> datasMap) throws IOException {
	// 参数定义
	String datas = String.valueOf(datasMap.get("datas"));
	String deviceMac = String.valueOf(datasMap.get("deviceMac"));
	String deviceSn = String.valueOf(datasMap.get("deviceSn"));
	String deviceTypes = String.valueOf(datasMap.get("deviceTypes"));
	String createDateStart = String.valueOf(datasMap.get("createDateStart"));
	String createDateEnd = String.valueOf(datasMap.get("createDateEnd"));
	// 分页参数
	String order = String.valueOf(datasMap.get("order"));
	String orderColumn = String.valueOf(datasMap.get("orderColumn"));
	Integer page = datasMap.get("pageNum") != null ? Integer.valueOf(String.valueOf(datasMap.get("pageNum"))) : 1;
	Integer size = datasMap.get("pageSize") != null ? Integer.valueOf(String.valueOf(datasMap.get("pageSize"))) : 10;

	
	List<String> deviceTypeList = null;
	if(StringUtils.isNotBlank(deviceTypes) && !(deviceTypes.equalsIgnoreCase("null"))) {
		deviceTypeList = Arrays.asList(deviceTypes.split(","));
	}
	
	// 封装查询条件
	BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
	// 多字段查询 
	BoolQueryBuilder multiBuilder = new BoolQueryBuilder();
	// 多值查询
	BoolQueryBuilder termsBuilder = new BoolQueryBuilder();
	// 模糊查询
	BoolQueryBuilder matchBuilder = new BoolQueryBuilder();
	// 精确查询
	BoolQueryBuilder termBuilder = new BoolQueryBuilder();
       
       if(StringUtils.isNotBlank(datas) && !(datas.equalsIgnoreCase("null"))) {
       	// 在discusspost索引的datas和deviceMac字段中都查询 datas 
       	multiBuilder.should(QueryBuilders.multiMatchQuery(datas, "datas", "deviceMac"));
       	queryBuilder.must(multiBuilder);
       }
       
       if(deviceTypeList != null && deviceTypeList.size() > 0) {
       	// 在discusspost索引的deviceType字段中都查询多个值
       	termsBuilder.should(QueryBuilders.termsQuery("deviceType",deviceTypeList));
       	queryBuilder.must(termsBuilder);
       }
       
       if(StringUtils.isNotBlank(deviceMac) && !(deviceMac.equalsIgnoreCase("null"))) {
       	// 模糊查询
       	matchBuilder.should(QueryBuilders.matchQuery("deviceMac",deviceMac));
       	queryBuilder.must(matchBuilder);
       }
       
       if(StringUtils.isNotBlank(deviceSn) && !(deviceSn.equalsIgnoreCase("null"))) {
       	// 精准查询
       	termBuilder.should(QueryBuilders.termQuery("deviceSn",deviceSn));
       	queryBuilder.must(termBuilder);
       }
       
       // 时间范围筛选
	if (StringUtils.isNotBlank(createDateStart) && StringUtils.isNotBlank(createDateEnd)
	  		&& !(createDateStart.equalsIgnoreCase("null")) && !(createDateEnd.equalsIgnoreCase("null"))){
	  
		queryBuilder
			.filter(QueryBuilders.rangeQuery("createDate")
					.gte(DateUtil.parseStrToDate(createDateStart,DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS))
					.lte(DateUtil.parseStrToDate(createDateEnd,DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS)));
	}
	
	// 构建搜索条件
     SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
             .sort(SortBuilders.fieldSort(orderColumn).order(SortOrder.fromString(order)))
             // 一个可选项,用于控制允许搜索的时间:searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
             // 查询条件
             .query(queryBuilder)
             .from((page - 1) * size) // 指定从哪条开始查询 从0开始计算 第一页是 0
             .size(size); // 需要查出的总记录条数
	
	// discusspost是索引名
	SearchRequest searchRequest = new SearchRequest("discusspost");
	searchRequest.source(searchSourceBuilder);
	SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

	List<DatasEntity> list = new LinkedList<>();
	
	for (SearchHit hit : searchResponse.getHits().getHits()) {
		DatasEntity discussPost = JSONObject.parseObject(hit.getSourceAsString(), DatasEntity.class);
		list.add(discussPost);
	}
	
	// 返回参数
	Map<String,Object> map = new HashMap<>();
	
	map.put("data", list);
	map.put("page", page);
	map.put("size", size);
	map.put("total", searchResponse.getHits().getTotalHits().value);
	return map;
}
postman测试

在这里插入图片描述

第二种

service层

导包

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;

代码

@Autowired
private DatasMapper datasMapper;

@Override
public Map<String,Object> query(Map<String,Object> datasMap) throws IOException {
	
	// 参数定义
	String datas = String.valueOf(datasMap.get("datas"));
	String deviceTypes = String.valueOf(datasMap.get("deviceTypes"));
	String createDateStart = String.valueOf(datasMap.get("createDateStart"));
	String createDateEnd = String.valueOf(datasMap.get("createDateEnd"));
	// 分页参数
	String order = String.valueOf(datasMap.get("order"));
	String orderColumn = String.valueOf(datasMap.get("orderColumn"));
	Integer page = datasMap.get("page") != null ? Integer.valueOf(String.valueOf(datasMap.get("page"))) : 1;
	Integer size = datasMap.get("size") != null ? Integer.valueOf(String.valueOf(datasMap.get("size"))) : 10;

	List<String> deviceTypeList = null;
	if(StringUtils.isNotBlank(deviceTypes) && !(deviceTypes.equalsIgnoreCase("null"))) {
		deviceTypeList = Arrays.asList(deviceTypes.split(","));
	}
	
	// 查询对象
	BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
	// 模糊搜索对象
	BoolQueryBuilder keyBuilder = new BoolQueryBuilder();
	// 分类查询对象
	BoolQueryBuilder orBuilder = new BoolQueryBuilder();
	
	// 拼接模糊搜索条件
	if (StringUtils.isNotBlank(datas) && !(datas.equalsIgnoreCase("null"))){
	    // 这边主要用的是should,也就是相当于mysql的or   datas like concat('%datas%') or deviceMac like concat('%datas%') 
	    // wildcardQuery可以用于带分词的模糊搜索,如果要分词的话,那么字段的type应该是text,假如在用wildcardQuery而不想分词的话,可以查.keyword
	    // 例如title.keyword,不过我这边title的type已经定了是keyword类型,所以我就直接做不分词的模糊查询,精确查询的话就用matchQuery
	    keyBuilder.should(QueryBuilders.wildcardQuery("datas", "*" + datas + "*"));
	    keyBuilder.should(QueryBuilders.wildcardQuery("deviceMac", "*" + datas + "*"));
	    queryBuilder.must(keyBuilder);
	}
	
	// 拼接分类筛选条件
	if (deviceTypeList != null && deviceTypeList.size() > 0){
	    // 这里主要是实现了多条件筛选的需求,前端有复选框的条件筛选,后端以集合方式接收,然后做or的条件拼接 deviceType = '1' or deviceType = '2'...
		deviceTypeList.forEach(s -> orBuilder.should(QueryBuilders.matchQuery("deviceType",s)));
	    queryBuilder.must(orBuilder);
	}
	
	// 时间范围筛选
	if (StringUtils.isNotBlank(createDateStart) && StringUtils.isNotBlank(createDateEnd)
			&& !(createDateStart.equalsIgnoreCase("null")) && !(createDateEnd.equalsIgnoreCase("null"))){
	    // 范围筛选就用rangeQuery 相当于 >= 'xx' and <= 'xx'   还有gt方法和lt方法就是不带 '='
	    QueryBuilder queryRange = QueryBuilders.rangeQuery("createDate")
	            .gte(DateUtil.parseStrToDate(createDateStart,DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS))
	            .lte(DateUtil.parseStrToDate(createDateEnd,DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS));
	    queryBuilder.filter(queryRange);
	}
	// 分页查询并按发布时间排序
	// 这边的分页只能算是form-size的浅层分页,如果数据量大的话,建议改造成scroll深度分页
	// 这里 page 要减一
	Pageable pageable = PageRequest.of(page - 1, size, Direction.fromString(order),orderColumn);
	Page<DatasEntity> search = datasMapper.search(queryBuilder,pageable);
	
	Map<String ,Object> map = new HashMap<>();
	map.put("data", search);
	return map;
}
Dao层

导包

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Component;

代码

@Component
public interface DatasMapper extends ElasticsearchRepository<DatasEntity,Long>  {
}
postman测试

在这里插入图片描述
参考博客:

https://blog.csdn.net/wpw2000/article/details/115704320
https://www.jb51.net/article/211278.htm
https://blog.csdn.net/weixin_45566249/article/details/111297868
https://blog.csdn.net/BiandanLoveyou/article/details/115874372
https://www.jianshu.com/p/86afb2d3bd27

### 实现Spring Boot Elasticsearch分页查询 在Spring Boot应用程序中集成Elasticsearch并实现分页查询涉及多个方面。为了简化配置过程,Spring Boot会自动选择最近版本的Spring Data模块[^1]。 对于分页功能的支持,`Pageable`接口提供了必要的参数来定义分页行为。下面是一个简单的例子展示如何在一个控制器方法中利用`PagedResourcesAssembler`来进行分页处理: ```java import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.hateoas.PagedModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @RestController @RequestMapping("/elasticsearch") public class ElasticSearchController { private final ProductRepository productRepository; // 假设有一个Product实体类及其对应的仓库 @Autowired public ElasticSearchController(ProductRepository productRepository) { this.productRepository = productRepository; } /** * 获取带有分页的产品列表. */ @GetMapping("/products") public PagedModel<EntityModel<Product>> getProducts( @RequestParam(value="page", defaultValue="0") int page, @RequestParam(value="size", defaultValue="20") int size) { PageRequest pageable = PageRequest.of(page, size); Page<Product> productsPage = productRepository.findAll(pageable); var assembler = new EntityModelAssembler<>(product -> Link.of("/elasticsearch/products/" + product.getId())); return pagedResourcesAssembler.toModel(productsPage, assembler); } } ``` 上述代码片段展示了如何通过RESTful API端点接收分页请求,并返回经过HATEOAS增强后的资源集合。这里使用了`EntityModelAssembler`来自定义链接生成逻辑;而`pagedResourcesAssembler.toModel()`则负责将`Page<T>`转换成适合客户端消费的形式[^4]。 此外,在实际应用开发过程中可能还需要考虑其他因素,比如性能优化、错误处理机制以及安全性等方面的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值