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

  • ​why elasticsearch​
  • ​concept​
  • ​how use elasticsearch​
  • 版本说明
  • 安装说明
  • 创建索引和映射
  • 新建Repository
  • 新建 service接口和实现类
  • POJO 封装对象
  • 测试

  • why elasticsearch

    Elasticsearch 不多说了:

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

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

    concept
    1. Elasticsearch很多概念与MySQL类似的,对比关系:

    ES

    Mysql

    索引库(indices)

    Databases 数据库

    类型(type)

    Table 数据表

    文档(Document)

    Databases 数据库

    字段(Field)

    Column 列

    1. Es的映射配置(mappings)
      指设置字段的数据类型、属性、是否索引、是否存储等特性。
    2. 集群相关的概念:
      索引集(Indices,index的复数):逻辑上的完整索引
      分片(shard):数据拆分后的各个部分
      副本(replica):每个分片的复制
    3. 分布式说明
      Es本身分布式,即便你只有一个节点,默认也会对你的数据进行分片和副本操作,
      当你向集群添加新数据时,数据也会在新加入的节点中进行平衡(负载均衡)。
    4. 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

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

      进入config目录, 修改一下 IKAnalyzer.cfg.xml 将扩展的分词库加入进去 ,然后重启es即可,不然像停顿词 “的地得” 还是会做倒排索引的

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

      创建索引和映射

      这里说一下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));
      }

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

  •