SpringBoot Elasticsearch 7.x 多条件分页查询
本文目的记录下新版本的Elasticsearch API查询使用
目录
- why elasticsearch
- concept
- how use elasticsearch
- 版本说明
- 安装说明
- 创建索引和映射
- 新建Repository
- 新建 service接口和实现类
- POJO 封装对象
- 测试
why elasticsearch
Elasticsearch 不多说了:
- 用途广泛,社区活跃,Apache开源许可免费
- 分布式多用户能力的全文搜索引擎
- 可RESTful web接口,可多语言API接口
- 它是用Java语言开发的
- 企业级搜索引擎
我这里主要用作全文搜索引擎,市面上也有不少其他的简单易用的搜索引擎,不过考虑社区活跃程度,大众认知程度最终还是选择elasticsearch ,主要它的组合ELK可以作为分布式日志管理工具。
concept
- Elasticsearch很多概念与MySQL类似的,对比关系:
ES | Mysql |
---|---|
索引库(indices) | Databases 数据库 |
类型(type) | Table 数据表 |
文档(Document) | Databases 数据库 |
字段(Field) | Column 列 |
- Es的映射配置(mappings)
指设置字段的数据类型、属性、是否索引、是否存储等特性。 - 集群相关的概念:
索引集(Indices,index的复数):逻辑上的完整索引
分片(shard):数据拆分后的各个部分
副本(replica):每个分片的复制 - 分布式说明
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));
}
还没有评论,来说两句吧...