SpringBootData整合Elasticsearch实现参数查询、自定义分页查询

😊 @ 作者: 一恍过去
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: SpringBootData整合Elasticsearch
⏱️ @ 创作时间: 2022年08月26日

在这里插入图片描述

1、POM

<!--es-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

2、YAML配置

spring:
  elasticsearch:
    rest:
      # 集群连接以","逗号分割http://192.168.80.121:9211,http://192.168.80.121:9212,http://192.168.80.121:9213
      uris: http://192.168.80.121:9200
      #username: user
      #password: pwd
      # 默认1S
      connection-timeout: 2
      # 默认30S
      read-timeout: 10

3、封装Builder构造器

ConditionQueryBuilder:


import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.RangeQueryBuilder;

/**
 * @Author: 
 * @Date: 2022/8/25 16:46
 * @Description: 条件构造器
 **/
public class ConditionQueryBuilder {

    private ConditionQueryBuilder() {

    }

    private static ConditionQueryBuilder conditionQueryBuilder;

    public static synchronized ConditionQueryBuilder getInstance() {
        if (conditionQueryBuilder == null) {
            conditionQueryBuilder = new ConditionQueryBuilder();
        }
        return conditionQueryBuilder;
    }

    /**
     * 匹配(等于)查询台条件
     *
     * @param filed
     * @param value
     */
    public MatchQueryBuilder match(String filed, String value) {
        return new MatchQueryBuilder(filed, value);
    }

    /**
     * 大于
     *
     * @param filed
     * @param value
     */
    public RangeQueryBuilder gt(String filed, Object value) {
        return new RangeQueryBuilder(filed).gt(value);
    }

    /**
     * 大于等于
     *
     * @param filed
     * @param value
     */
    public RangeQueryBuilder gte(String filed, Object value) {
        return new RangeQueryBuilder(filed).gte(value);
    }


    /**
     * 小于
     *
     * @param filed
     * @param value
     */
    public RangeQueryBuilder lt(String filed, Object value) {
        return new RangeQueryBuilder(filed).lt(value);
    }

    /**
     * 小于等于
     *
     * @param filed
     * @param value
     */
    public RangeQueryBuilder lte(String filed, Object value) {
        return new RangeQueryBuilder(filed).lte(value);
    }
}

HighlightFiledBuilder:


import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;

/**
 * @Author: 
 * @Date: 2022/8/25 14:54
 * @Description: 高亮构造器
 **/
public class HighlightFiledBuilder {
    /**
     * 高亮的html开始
     */
    private final String preTag = "<font style='color:red'>";

    /**
     * 高亮的html结束
     */
    private final String postTag = "</font>";

    /**
     * 高亮字段名称
     *
     * @param filed
     * @return
     */
    public HighlightBuilder.Field highlightBuild(String filed) {
        return new HighlightBuilder.Field(filed).preTags(preTag).postTags(postTag);
    }

}

PageBuilder:

import org.springframework.data.domain.PageRequest;

/**
 * @Author: 
 * @Date: 2022/8/25 14:54
 * @Description: 分页构造器
 **/
public class PageBuilder {
    public PageRequest startPage(Integer pageSize, Integer pageNum) {
        if (pageSize == null || pageNum == null) {
            pageSize = 10;
            pageNum = 1;
        }
        return PageRequest.of(pageNum - 1, pageSize);
    }
}

NewPageBean:

import com.lhz.search.model.param.PageParam;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author:
 * @Date:
 * @Version: 1.0
 **/
public class NewPageBean<T> {
    private Long total;
    private List<T> list;
    private List<Map<String, String>> highList;
    private MyPage pageInfo;


    public NewPageBean(PageParam param, SearchHits<T> search) {
        Integer pageNum = param.getPageNum();
        Integer pageSize = param.getPageSize();


        long totalSize = search.getTotalHits();

        //计算总页数
        long totalPage = totalSize % pageSize == 0 ? totalSize / pageSize : totalSize / pageSize + 1;

        // 查询结果字符串拼接
        List<SearchHit<T>> searchHits = search.getSearchHits();

        List<T> objList = new ArrayList<>();
        try {
            for (SearchHit hit : searchHits) {
                T content = (T) hit.getContent();
                objList.add(content);
                Class<?> clazz = content.getClass();
                // 由于使用了ES则数据量肯定大,必定会分页,所以在分页中 解析高亮字段
                Map<String, List<String>> highlightFields = hit.getHighlightFields();
                for (Map.Entry<String, List<String>> entry : highlightFields.entrySet()) {
                    String field = entry.getKey();
                    String value = entry.getValue().get(0);
                    Field cField = clazz.getDeclaredField(field);
                    cField.setAccessible(true);
                    cField.set(content, value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("ES分页错误");
        }
        this.list = objList;

        this.total = totalSize;
        MyPage page = new MyPage();
        page.setTotalPage(totalPage);
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        page.setPreviousPage(pageNum - 1);
        page.setNextPage(pageNum + 1);
        this.pageInfo = page;
    }

    public long getNum() {
        return this.total;
    }

    public void setNum(Long total) {
        this.total = total;
    }

    public Long getTotal() {
        return total;
    }

    public void setTotal(Long total) {
        this.total = total;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public MyPage getPageInfo() {
        return pageInfo;
    }

    public void setPageInfo(MyPage pageInfo) {
        this.pageInfo = pageInfo;
    }

    public List<Map<String, String>> getHighList() {
        return highList;
    }

    public void setHighList(List<Map<String, String>> highList) {
        this.highList = highList;
    }

    class MyPage {
        private int pageNum;
        private int pageSize;
        private long totalPage;
        private int nextPage;
        private int previousPage;

        public int getPageNum() {
            return pageNum;
        }

        public void setPageNum(int pageNum) {
            this.pageNum = pageNum;
        }

        public int getPageSize() {
            return pageSize;
        }

        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }

        public long getTotalPage() {
            return totalPage;
        }

        public void setTotalPage(long totalPage) {
            this.totalPage = totalPage;
        }

        public int getNextPage() {
            return nextPage;
        }

        public void setNextPage(int nextPage) {
            this.nextPage = nextPage;
        }

        public int getPreviousPage() {
            return previousPage;
        }

        public void setPreviousPage(int previousPage) {
            this.previousPage = previousPage;
        }
    }
}

4、封装实体类

ProductAgg:

import lombok.Data;

/**
 * @Author: 
 * @Date: 2022/8/25 22:19
 * @Description:
 **/
@Data
public class ProductAgg {
    /**
     * 聚合字段属性值
     */
    private String name;

    /**
     * 数量
     */
    private Long count;

    /**
     * 聚合结果
     */
    private Object value;
}

ProductEntity:


import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * @Author: 
 * @Date: 2022/8/25 21:31
 * @Description: 创建索引为 shop_product,主分片3个,副本2个
 **/
@Document(indexName = "shop_product", shards = 3, replicas = 2)
@Data
public class ProductEntity implements Serializable {

    @Id
    private String id;

    /**
     * 文本类型,使用ik分词器
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;

    /**
     * 价格
     */
    @Field(type = FieldType.Double)
    private BigDecimal money;

    /**
     * 时间戳
     */
    @Field(type = FieldType.Long)
    private Long time;

    /**
     * 颜色
     */
    @Field(type = FieldType.Keyword)
    private String color;
}

5、封装请求类

PageParam:


import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @Author: 
 * @Date: 2020/11/14 17:33
 * @Description:
 **/
@Data
public class PageParam {

    @ApiModelProperty("当前页码")
    private Integer pageNum;

    @ApiModelProperty("每页数量")
    private Integer pageSize;
}

ProductQueryParam:

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.Date;


/**
 * @author 
 */
@ApiModel("ES查询参数")
@EqualsAndHashCode(callSuper = true)
@Data
public class ProductQueryParam extends PageParam {


    @ApiModelProperty(value = "名称")
    private String name;

    @ApiModelProperty(value = "最小金额")
    private Double minMoney;
}

在这里插入图片描述

5、repository

import com.lhz.search.model.entity.ProductEntity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

/**
 * @Author: 
 * @Date: 2022/8/25 21:52
 * @Description:
 **/
public interface ProductRepository extends ElasticsearchRepository<ProductEntity, String> {
    
}

6、Service

ProductService:


import com.lhz.search.model.entity.ProductEntity;
import com.lhz.search.model.param.ProductQueryParam;

/**
 * @Author: 
 * @Date: 2022/8/25 21:59
 * @Description:
 **/
public interface ProductService {
    /**
     * 新增
     */
    void save();

    /**
     * 修改
     *
     * @param productEntity
     */
    void update(ProductEntity productEntity);

    /**
     * 根据Id查询
     *
     * @param id
     * @return
     */
    Object selectById(String id);

    /**
     * 查询所有
     * @return
     */
    Object selectByAll();

    /**
     * 根据Id删除
     *
     * @param id
     */
    void deleteById(String id);


    /**
     * 复制条件查询
     *
     * @param param
     * @return
     */
    Object listQuery(ProductQueryParam param);

    /**
     * 聚合
     *
     * @return
     */
    Object agg();

}

ProductServiceImpl:


import com.lhz.search.builder.ConditionQueryBuilder;
import com.lhz.search.builder.HighlightFiledBuilder;
import com.lhz.search.builder.PageBuilder;
import com.lhz.search.model.entity.ProductAgg;
import com.lhz.search.model.entity.ProductEntity;
import com.lhz.search.model.page.NewPageBean;
import com.lhz.search.model.param.ProductQueryParam;
import com.lhz.search.repository.ProductRepository;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedSum;
import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author:
 * @Date: 2022/8/25 22:00
 * @Description:
 **/
@Service
public class ProductServiceImpl implements ProductService {

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Resource
    private ProductRepository productRepository;

    /**
     * 新增
     */
    @Override
    public void save() {
        String[] colors = {"red", "purple", "blue"};
        String[] name = {"手机", "电脑", "微波炉", "电视"};
        // 新增列表数据
        List<ProductEntity> testList = new ArrayList<>();
        long sys = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            // 模拟随机数
            double random = Math.random() * 1000;

            ProductEntity productEntity = new ProductEntity();
            productEntity.setId(i + "");
            productEntity.setName(name[i % 4]);
            productEntity.setMoney(new BigDecimal(random));
            productEntity.setTime(sys + i % 3);
            productEntity.setColor(colors[i % 3]);
            testList.add(productEntity);
        }
        // 新增成功后,在head或者kibana中就可以看到相应数据,如果save时数据Id存在则更新操作
        // 批量新增
        productRepository.saveAll(testList);

        // 单个新增
        ProductEntity productEntity = testList.get(9);
        productEntity.setId("11");
        productRepository.save(productEntity);
    }

    /**
     * 修改
     *
     * @param productEntity
     */
    @Override
    public void update(ProductEntity productEntity) {
        productRepository.save(productEntity);
    }

    /**
     * 查询所有
     *
     * @return
     */
    @Override
    public Object selectByAll() {
        return productRepository.findAll();
    }

    /**
     * 根据Id查询
     *
     * @param id
     * @return
     */
    @Override
    public Object selectById(String id) {
        return productRepository.findById(id);
    }

    /**
     * 根据Id删除
     *
     * @param id
     */
    @Override
    public void deleteById(String id) {
        productRepository.deleteById(id);
        // 批量删
        // esTestRepository.deleteAll(List<ProductEntity>);
    }

    /**
     * 复杂条件查询
     * SearchQuery 总的查询
     * BoolQueryBuilder 条件查询,可在后面加上must,mustNot,should等等
     * MatchQueryBuilder 匹配查询
     * TermQueryBuilder 倒排索引查询
     * HighlightBuilder 高亮查询,用于设置要高亮的field
     *
     * @param param
     * @return
     */
    @Override
    public Object listQuery(ProductQueryParam param) {
        // 分页参数
        PageRequest pageRequest = new PageBuilder().startPage(param.getPageSize(), param.getPageNum());

        // 高亮字段 - 如果需要则配置(非必要)
        String lightFiled = "name";
        HighlightBuilder.Field highlightBuilder = new HighlightFiledBuilder().highlightBuild(lightFiled);

        // 条件查询
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        ConditionQueryBuilder conditionQueryBuilder = ConditionQueryBuilder.getInstance();
        // 比如:name必须包含“xxx”并且金额大于xx
        if (param.getMinMoney() != null) {
            boolQueryBuilder.must(conditionQueryBuilder.gte("money", param.getMinMoney()));
        }
        if (param.getName() != null) {
            boolQueryBuilder.must(conditionQueryBuilder.match("name", param.getName()));
        }

        //查询实例
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                // 设置条件
                .withQuery(boolQueryBuilder)
                .withQuery(boolQueryBuilder)
                // 简易条件,单一条件直接设置
                // .withQuery(conditionQueryBuilder.match("name", "手机"))
                //设置高亮效果
                .withHighlightFields(highlightBuilder)
                //分页
                .withPageable(pageRequest)
                // 排序,分词的字段不能排序;比如:按time字段排序;ASC-升序,DESC-降序
                .withSort(SortBuilders.fieldSort("time").order(SortOrder.DESC))
                // 多字段排序
                .withSort(SortBuilders.fieldSort("money").order(SortOrder.ASC))
                .build();
        SearchHits<ProductEntity> search = elasticsearchRestTemplate.search(query, ProductEntity.class);
        // 封装分页参数
        return new NewPageBean<>(param, search);
    }

    /**
     * 分组聚合计算,先分组然后聚合计算
     * 聚合可用直接进行terms聚合(分组查询),AggregationBuilders.terms("time").field("time");
     * 聚合可用直接进行聚合计算(直接使用sum、max、min等), AggregationBuilders.sum("money").field("money");
     *
     * @return
     */
    @Override
    public Object agg() {
        // 构建聚合,聚合之间可以进行嵌套聚合,如果要先分组然后sum或者avg时,需要嵌套
        // 分组。terms分组名称、field分组字段;默认分组后的结果为count
        TermsAggregationBuilder builder = AggregationBuilders.terms("colorAgg").field("color");
        // 其他聚合,比如:AVG/SumAggregationBuilder等
        SumAggregationBuilder sumBuilder = AggregationBuilders.sum("sumMoney").field("money");
        builder.subAggregation(sumBuilder);

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .addAggregation(builder)
                .build();
        SearchHits<ProductEntity> search = elasticsearchRestTemplate.search(query, ProductEntity.class);
        Aggregations aggregations = search.getAggregations();
        if (aggregations != null) {
            Terms terms = (Terms) aggregations.asMap().get("colorAgg");
            List<ProductAgg> productAggList = new ArrayList<>();
            // 遍历取出聚合字段列的值,与对应的数量
            for (Terms.Bucket bucket : terms.getBuckets()) {
                ProductAgg productAgg = new ProductAgg();
                productAgg.setName(bucket.getKeyAsString());
                productAgg.setCount(bucket.getDocCount());
                // 默认value就是count
                productAgg.setValue(bucket.getDocCount());
                // 解析嵌套聚合
                Aggregations sumAggregations = bucket.getAggregations();
                if (sumAggregations != null) {
                    // ParsedXxx的选择需要根据聚合计算的方式来定
                    ParsedSum parsedSum = sumAggregations.get("sumMoney");
                    productAgg.setValue(parsedSum.getValueAsString());
                }
                productAggList.add(productAgg);
            }
            return productAggList;
        } else {
            return search;
        }
    }
}

7、Controller


import com.lhz.search.model.entity.ProductEntity;
import com.lhz.search.model.param.ProductQueryParam;
import com.lhz.search.service.ProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @Author:
 * @Date: 2020/6/25 23:07
 * @Description: 对ES的操作,数据操作之前需要进行索引的创建
 **/
@Api(tags = "ES管理")
@ApiSort(10)
@RestController
@RequestMapping("es")
@Slf4j
public class ProductController {

    @Resource
    private ProductService productService;


    @ApiOperation(value = "新增文档数据", notes = "新增文档数据")
    @PostMapping
    public void save() {
        productService.save();
    }

    @ApiOperation(value = "修改文档数据", notes = "修改文档数据")
    @PutMapping
    public void update(@RequestBody ProductEntity productEntity) {
        productService.update(productEntity);
    }

    @ApiOperation(value = "查看所有文档", notes = "查看所有文档")
    @GetMapping("/all")
    public Object selectByAll() {
        return productService.selectByAll();
    }

    @ApiOperation(value = "根据Id查看文档", notes = "根据Id查看文档")
    @GetMapping("/{id}")
    public Object selectById(@PathVariable("id") String id) {
        return productService.selectById(id);
    }

    @ApiOperation(value = "删除文档", notes = "删除文档")
    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") String id) {
        productService.deleteById(id);
    }

    @ApiOperation(value = "自定义查询", notes = "自定义查询")
    @GetMapping("/list")
    public Object listQuery(ProductQueryParam param) {
        return productService.listQuery(param);
    }

    @ApiOperation(value = "聚合查询", notes = "聚合查询")
    @PostMapping("/agg")
    public Object agg() {
        return productService.agg();
    }
}

8、完整代码

完整代码参考地址:https://gitee.com/lhzlx/spring-data-elasticsearch.git

在这里插入图片描述

在使用ElasticsearchRepository进行条件查询时,可以通过使用RangeQueryBuilder来实现大于小于的查询。RangeQueryBuilder是Elasticsearch中的一个查询构造器,用于构造范围查询条件。例如,如果要查询某个字段大于某个值的文档,可以使用RangeQueryBuilder的gt方法指定大于的值。类似地,如果要查询某个字段小于某个值的文档,可以使用RangeQueryBuilder的lt方法指定小于的值。以下是一个示例代码,演示了如何使用RangeQueryBuilder实现大于小于的条件查询: ``` public RestResult rangeQuery(String field, Object lowerValue, Object upperValue) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(field) .gt(lowerValue) .lt(upperValue); Iterable<NewInfoItem> search = this.newsInfoRepository.search(rangeQueryBuilder); List<NewInfoItem> resultList = new ArrayList<>(); search.forEach(resultList::add); return interReturnResult(GlobalErrorCode.SUCCESS, resultList); } ``` 在上述代码中,field表示要查询的字段名,lowerValue和upperValue分别表示查询的下限和上限。通过调用rangeQueryBuilder的gt和lt方法,可以构造出大于lowerValue并且小于upperValue的查询条件。然后,通过调用newsInfoRepository的search方法执行查询,并将结果返回。 注意:具体的查询逻辑和参数可能需要根据实际的业务需求进行调整。以上只是一个示例,供参考。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [ElasticSearch集成SpringData史上最全查询教程](https://blog.csdn.net/CharlesYooSky/article/details/120331397)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [SpringBootData整合Elasticsearch实现参数查询自定义分页查询](https://blog.csdn.net/zhuocailing3390/article/details/126375271)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一恍过去

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值