SpringBoot Elasticsearch 7.x 多条件分页查询

本文目的记录下新版本的Elasticsearch API查询使用

why elasticsearch

Elasticsearch 不多说了:

  • 用途广泛,社区活跃,Apache开源许可免费
  • 分布式多用户能力的全文搜索引擎
  • 可RESTful web接口,可多语言API接口
  • 它是用Java语言开发的
  • 企业级搜索引擎

我这里主要用作全文搜索引擎,市面上也有不少其他的简单易用的搜索引擎,不过考虑社区活跃程度,大众认知程度最终还是选择elasticsearch ,主要它的组合ELK可以作为分布式日志管理工具。

concept

  1. Elasticsearch很多概念与MySQL类似的,对比关系:
ESMysql
索引库(indices)Databases 数据库
类型(type)Table 数据表
文档(Document)Databases 数据库
字段(Field)Column 列
  1. Es的映射配置(mappings)
    指设置字段的数据类型、属性、是否索引、是否存储等特性。

  2. 集群相关的概念:
    索引集(Indices,index的复数):逻辑上的完整索引
    分片(shard):数据拆分后的各个部分
    副本(replica):每个分片的复制

  3. 分布式说明
    Es本身分布式,即便你只有一个节点,默认也会对你的数据进行分片和副本操作,
    当你向集群添加新数据时,数据也会在新加入的节点中进行平衡(负载均衡)。

how use elasticsearch

版本说明

软件环境:
JDK1.8
SpringBoot 2.3.8

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

ES, 这里和SpringBoot 对应的默认版本是 7.6.2

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

springboot yml 文件配置说明:

spring:
  elasticsearch:
    rest:
      uris: http://你的es服务器地址:9200

安装说明

ES 的安装步骤省略…网上一搜一大把,这里是docker单机版本的

docker run -p 9200:9200 -p 9300:9300 -d --name=es -e "ES_JAVA_OPTS=-Xms512m -Xmx512m"  -e "discovery.type=single-node" elasticsearch:7.11.2

这里重点说一下中文分词:
ElasticSearch 默认采用分词器, 单个字分词 ,效果很差。

下面是es中文分词库,与Elasticsearch一起维护升级,版本也保持一致
https://github.com/medcl/elasticsearch-analysis-ik/releases

下载 zip压缩文件,直接解压到 elasticsearch/plugins 目录下新建一个文件夹名称为 ik
在这里插入图片描述
进入config目录,修改一下 IKAnalyzer.cfg.xml 将扩展的分词库加入进去,然后重启es即可,不然像停顿词 “的地得” 还是会做倒排索引的
在这里插入图片描述

创建索引和映射

这里说一下es7.x 相对于 6.x ,很多api过时了,需要使用新的。

这里测试创建一个索引叫“documentlibrary” ,存放各类文档。

重点说明:

如果是单机版本的一台服务器上的 es,那么 创建的index中 replicas  必须为0,

因为es的分片副本不可和原分片在同一个节点上(即同一台服务或同一个虚拟操作系统中)

import io.swagger.annotations.ApiModelProperty;
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;

/**
 * 针对文档库中的文档存储在 es中的对象信息
 *
 * @author admin
 */
@Document(indexName = "documentlibrary", shards = 2, replicas = 0)
@Data
public class DocumentLibrary implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @ApiModelProperty(value = "主键")
    @Id
    private Long id;

    /**
     * t_oss_upload_record 表id
     */
    @ApiModelProperty(value = "t_oss_upload_record 表id")
    @Field(name = "ossUploadRecord", type = FieldType.Long)
    private Long ossUploadRecord;

    /**
     * 文件分类
     */
    @ApiModelProperty(value = "文件分类")
    @Field(name = "categories", type = FieldType.Keyword)
    private String categories;

    /**
     * 原始文件名称
     */
    @ApiModelProperty(value = "原始文件名称")
    @Field(name = "originalFileName", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String originalFileName;

    /**
     * 上传服务器的最终地址
     */
    @ApiModelProperty(value = "上传服务器的最终地址")
    @Field(name = "resultPath", type = FieldType.Keyword)
    private String resultPath;

    /**
     * 摘要
     */
    @ApiModelProperty(value = "摘要")
    @Field(name = "summary", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String summary;

    /**
     * 整体内容抽取
     */
    @ApiModelProperty(value = "【一般不展示该字段,只存储使用】整体内容抽取")
    @Field(name = "allContent", type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String allContent;

    /**
     * 版本
     */
    @ApiModelProperty(value = "版本")
    @Field(name = "rversion", type = FieldType.Keyword)
    private String rversion;

    /**
     * 创建人
     */
    @ApiModelProperty(value = "创建人")
    @Field(name = "createUser", type = FieldType.Keyword)
    private String createUser;

    /**
     * 创建日期
     */
    @ApiModelProperty(value = "创建日期")
    @Field(name = "createTime", type = FieldType.Text, fielddata = true)
    private String createTime;

    /**
     * 文件类型后缀
     */
    @ApiModelProperty(value = "文件类型后缀")
    @Field(name = "fileType", type = FieldType.Keyword)
    private String fileType;

    /**
     * 关键字1
     */
    @ApiModelProperty("关键字1")
    @Field(name = "Keyword1", type = FieldType.Keyword)
    private String Keyword1;

    /**
     * 关键字1
     */
    @ApiModelProperty("关键字2")
    @Field(name = "Keyword2", type = FieldType.Keyword)
    private String Keyword2;

    /**
     * 关键字1
     */
    @ApiModelProperty("关键字1")
    @Field(name = "Keyword3", type = FieldType.Keyword)
    private String Keyword3;

    /**
     * 关键字4
     */
    @ApiModelProperty("关键字4")
    @Field(name = "Keyword4", type = FieldType.Keyword)
    private String Keyword4;

    /**
     * 关键字5
     */
    @ApiModelProperty("关键字5")
    @Field(name = "Keyword5", type = FieldType.Keyword)
    private String Keyword5;

}

新建Repository

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

/**
 * 针对文档库中的文档存储在 es中的对象信息
 *
 * @author guzt
 */
public interface DocumentLibraryRepository extends ElasticsearchRepository<DocumentLibrary, Long> {


}

新建 service接口和实现类

import com.middol.colburn.oss.model.client.es.DocumentLibrary;
import com.middol.colburn.oss.model.client.pojo.query.DocumentLibraryQuery;
import com.middol.starter.common.pojo.vo.PageVO;

/**
 * ES 服务类
 *
 * @author admin
 */
public interface DocumentLibraryService {

    /**
     * 保存一条数据类型
     *
     * @param entity DocumentLibrary
     */
    void save(DocumentLibrary entity);

    /**
     * 详细条件全文检索
     *
     * @param query DocumentLibraryQuery
     * @return PageInfo
     */
    PageVO<DocumentLibrary> search(DocumentLibraryQuery query);
}

下面的search 实现的查询功能如下:
如果传递了 keywordFilterTxt 参数则做全文所有字段检索,如果传递其他字段则另外还进行过滤操作

  • addFields 指的是查询出哪些字段
  • StrUtil 是hutools里面的工具类
  • init 是初始化创建索引和映射

import cn.hutool.core.util.StrUtil;
import com.middol.colburn.oss.model.client.es.DocumentLibraryRepository;
import com.middol.colburn.oss.model.client.es.DocumentLibrary;
import com.middol.colburn.oss.model.client.pojo.query.DocumentLibraryQuery;
import com.middol.colburn.oss.model.client.service.DocumentLibraryService;
import com.middol.starter.common.pojo.vo.PageVO;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
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.PostConstruct;
import java.util.List;
import java.util.stream.Collectors;

/**
 * ES 服务类
 *
 * @author admin
 */
@Service("documentLibraryServiceImpl")
@ConditionalOnClass({ElasticsearchRestTemplate.class})
public class DocumentLibraryServiceImpl implements DocumentLibraryService {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    final
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    final
    DocumentLibraryRepository documentLibraryRepository;

    public DocumentLibraryServiceImpl(ElasticsearchRestTemplate elasticsearchRestTemplate, DocumentLibraryRepository documentLibraryRepository) {
        this.elasticsearchRestTemplate = elasticsearchRestTemplate;
        this.documentLibraryRepository = documentLibraryRepository;
    }


    @PostConstruct
    public void init() {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(DocumentLibrary.class);
        if (!indexOperations.exists()) {
            // 创建索引,会根据Item类的@Document注解信息来创建
            boolean result = indexOperations.create();
            logger.info("创建 elasticsearch 索引 DocumentLibrary, 创建结果={}", result);
            if (!result) {
                throw new RuntimeException("创建 elasticsearch 索引失败");
            } else {
                // 配置映射,会根据Item类中的id、Field等字段来自动完成映射
                indexOperations.createMapping();
            }
        }
    }

    @Override
    public void save(DocumentLibrary entity) {
        elasticsearchRestTemplate.save(entity);
    }

    @Override
    public PageVO<DocumentLibrary> search(DocumentLibraryQuery query) {
        PageVO<DocumentLibrary> pageVO = new PageVO<>();
        if (query.getPageNum() == null || query.getPageNum().equals(0)) {
            query.setPageNum(1);
        }
        if (query.getPageSize() == null || query.getPageSize().equals(0)) {
            query.setPageNum(10);
        }

        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        BoolQueryBuilder filter = QueryBuilders.boolQuery();
        // 添加基本分词查询
        if (StrUtil.isNotBlank(query.getCategories())) {
            filter.must(QueryBuilders.termQuery("categories", query.getCategories()));
        }
        if (StrUtil.isNotBlank(query.getFileType())) {
            filter.must(QueryBuilders.termQuery("fileType", query.getFileType()));
        }
        if (StrUtil.isNotBlank(query.getOriginalFileName())) {
            filter.must(QueryBuilders.matchQuery("originalFileName", query.getOriginalFileName()));
        }
        if (StrUtil.isNotBlank(query.getRversion())) {
            filter.must(QueryBuilders.termQuery("rversion", query.getRversion()));
        }
        if (StrUtil.isNotBlank(query.getCreateBeginTime())) {
            filter.must(QueryBuilders.rangeQuery("createTime").gte(query.getCreateBeginTime() + " 00:00:00"));
        }
        if (StrUtil.isNotBlank(query.getCreateEndTime())) {
            filter.must(QueryBuilders.rangeQuery("createTime").lte(query.getCreateBeginTime() + " 23:59:59"));
        }
        if (StrUtil.isNotBlank(query.getKeywordFilterTxt())) {
            queryBuilder.withQuery(QueryBuilders.queryStringQuery(query.getKeywordFilterTxt()));
        }
        queryBuilder.withFilter(filter);
        queryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
        queryBuilder.withPageable(PageRequest.of(query.getPageNum() - 1, query.getPageSize()));

        NativeSearchQuery nativeSearchQuery = queryBuilder.build();
        nativeSearchQuery.addFields("id", "ossUploadRecord", "categories","originalFileName",
                "resultPath","summary","rversion","createUser","createTime","fileType",
                "Keyword1","Keyword2","Keyword3","Keyword4","Keyword5","Keyword6","Keyword7","Keyword8");
        // 使用ElasticsearchRestTemplate进行复杂查询
        SearchHits<DocumentLibrary> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, DocumentLibrary.class);
        if (searchHits.getTotalHits() > 0) {
            List<DocumentLibrary> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
            pageVO.setPageNum(query.getPageNum());
            pageVO.setPageSize(query.getPageSize());
            pageVO.setRows(searchProductList);
            pageVO.setTotal(searchHits.getTotalHits());
            pageVO.setPages((int) Math.ceil((double) pageVO.getTotal() / query.getPageSize()));
        }
        return pageVO;
    }

}

POJO 封装对象

这里的 POJO,query 和 pageVO 代码如下:


import io.swagger.annotations.ApiModelProperty;

import java.util.List;

/**
 * 数据库分页查询列表封装对象
 *
 * @param <T> 实体类对象
 * @author admin
 */
 @Data
public class PageVO<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 总记录数
     */
    @ApiModelProperty(value = "总记录数")
    private Long total;

    /**
     * 当前页
     */
    @ApiModelProperty(value = "当前页")
    private Integer pageNum;

    /**
     * 每页的数量
     */
    @ApiModelProperty(value = "每页的数量")
    private Integer pageSize;

    /**
     * 结果集
     */
    @ApiModelProperty(value = "结果集")
    private List<T> rows;

    /**
     * 总页数
     */
    @ApiModelProperty(value = "总页数")
    private Integer pages;
}

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

import java.io.Serializable;

/**
 * 文件上传记录(OssUploadRecord)表查询条件封装对象
 *
 * @author admin
 */
@Data
public class DocumentLibraryQuery implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty("当前页码,从1开始")
    private Integer pageNum = 1;

    @ApiModelProperty("每页大小,默认10")
    private Integer pageSize = 10;

    @ApiModelProperty(value = "文件分类")
    private String categories;

    @ApiModelProperty(value = "文件名称全文检索")
    private String originalFileName;

    @ApiModelProperty(value = "关键字全文检索")
    private String keywordFilterTxt;

    @ApiModelProperty(value = "版本")
    private String rversion;

    @ApiModelProperty(value = "创建日期 yyyy-mm-dd")
    private String createBeginTime;

    @ApiModelProperty(value = "结束日期 yyyy-mm-dd")
    private String createEndTime;

    @ApiModelProperty(value = "文件类型后缀")
    private String fileType;

}

测试

我这里写了一个controller方法:

    @ApiOperation(value = "测试ES", notes = "通过pagehelper插件进行物理分页")
    @ApiImplicitParam(name = "query", value = "前端参数封装", dataType = "DocumentLibraryQuery")
    @PostMapping(value = "testEs")
    public ResponseVO<PageVO<DocumentLibrary>> testEs(@RequestBody @Validated DocumentLibraryQuery query) {
        return ResponseVO.success(documentLibraryService.search(query));
    }

在这里插入图片描述

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值