Elasticsearch 是一款基于 Apache Lucene 的优秀的搜索服务器。本文将介绍基于 SpringBoot 2.5 集成 Elasticsearch 7 实现基本增删改查以及全文搜索。
Elasticsearch 官网: https://www.elastic.co
Elasticsearch 安装教程:
centOS 7 Elasticsearch 7.16 安装使用教程
./demo-elasticsearch/pom.xml
<!-- elasticsearch -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${elastic.springdata.version}</version>
</dependency>
其中 ${elastic.springdata.version}
的版本为 4.3.0
其他相关依赖
<!-- web mvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${springboot.version}</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Swagger 3,openApi 3 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox-swagger3.version}</version>
</dependency>
版本信息
<springboot.version>2.5.7</springboot.version>
<hutool.version>5.7.17</hutool.version>
<springfox-swagger3.version>3.0.0</springfox-swagger3.version>
注意事项:
Swagger 3.0 不支持 SpringBoot 2.6+
Springfox 3.0.0 is not working with Spring Boot 2.6.0
./demo-elasticsearch/src/main/resources/application.yml
## spring
spring:
application:
name: demo-elasticsearch
elasticsearch:
rest:
uris: 192.168.1.110:9200
username: elastic
password: elastic666
connection-timeout: 10S
read-timeout: 30S
sniffer:
interval: 5m
delay-after-failure: 1m
./demo-elasticsearch/src/main/java/com/ljq/springboot/elasticsearch/model/entity/BlogEntity.java
package com.ljq.springboot.elasticsearch.model.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.elasticsearch.annotations.Document;
/**
* @Description: 博客信息
* @Author: junqiang.lu
* @Date: 2021/11/13
*/
@Data
@ToString(callSuper = true)
@Document(indexName = "blog")
@ApiModel(value = "博客信息实体类", description = "博客信息实体类")
public class BlogEntity extends BaseEntity {
private static final long serialVersionUID = -2124422309475024490L;
/**
* 标题
*/
@ApiModelProperty(value = "标题", name = "title")
private String title;
/**
* 作者
*/
@ApiModelProperty(value = "作者", name = "author")
private String author;
/**
* 内容
*/
@ApiModelProperty(value = "内容", name = "content")
private String content;
/**
* 阅读数量
*/
@ApiModelProperty(value = "阅读数量", name = "countRead")
private Integer countRead;
/**
* 点赞数量
*/
@ApiModelProperty(value = "点赞数量", name = "countLike")
private Integer countLike;
/**
* 客户端时间戳(精确到秒)
*/
@ApiModelProperty(value = "客户端时间戳(精确到秒)", name = "clientTimestamp")
private Integer clientTimestamp;
}
./demo-elasticsearch/src/main/java/com/ljq/springboot/elasticsearch/model/entity/BaseEntity.java
package com.ljq.springboot.elasticsearch.model.entity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import java.io.Serializable;
import java.util.Date;
/**
* @Description: 基础实体类
* @Author: junqiang.lu
* @Date: 2021/9/24
*/
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = -3003658740476069858L;
/**
* id,主键
*/
@Id
@ApiModelProperty(value = "id,主键", name = "id")
private String id;
/**
* 创建时间
*/
@CreatedDate
@ApiModelProperty(value = "创建时间", name = "createTime")
private Long createTime;
/**
* 修改时间
*/
@LastModifiedDate
@ApiModelProperty(value = "修改时间", name = "updateTime")
private Date updateTime;
}
org.springframework.data.elasticsearch.annotations.Document
用于标注索引,类似于关系型数据库中的表名
org.springframework.data.annotation.Id
用于指定主键
org.springframework.data.elasticsearch.annotations.Field
用于指定字段,默认实体类中所有字段都会保存到 elasticsearch 中,所以可以不在字段上写这个注解
./demo-elasticsearch/src/main/java/com/ljq/springboot/elasticsearch/service/impl/BlogServiceImpl.java
package com.ljq.springboot.elasticsearch.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ljq.springboot.elasticsearch.common.api.ApiMsgEnum;
import com.ljq.springboot.elasticsearch.common.api.ApiResult;
import com.ljq.springboot.elasticsearch.model.entity.BlogEntity;
import com.ljq.springboot.elasticsearch.model.param.*;
import com.ljq.springboot.elasticsearch.service.BlogService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
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.elasticsearch.core.ElasticsearchRestTemplate;
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.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @Description: 博客业务实现类
* @Author: junqiang.lu
* @Date: 2021/12/11
*/
@Slf4j
@Service("blogService")
public class BlogServiceImpl implements BlogService {
@Autowired
private ElasticsearchRestTemplate elasticTemplate;
/**
* 新增单条
*
* @param addParam
* @return
*/
@Override
public ApiResult<BlogEntity> save(BlogAddParam addParam) {
BlogEntity blogEntity = new BlogEntity();
BeanUtil.copyProperties(addParam, blogEntity, CopyOptions.create().ignoreError().ignoreNullValue());
elasticTemplate.save(blogEntity);
return ApiResult.success(blogEntity);
}
/**
* 查询单条
*
* @param queryOneParam
* @return
*/
@Override
public ApiResult<BlogEntity> queryOne(BlogQueryOneParam queryOneParam) {
return ApiResult.success(elasticTemplate.get(queryOneParam.getId(), BlogEntity.class));
}
/**
* 更新单条
*
* @param updateParam
* @return
*/
@Override
public ApiResult<BlogEntity> update(BlogUpdateParam updateParam) {
BlogEntity blogEntity = elasticTemplate.get(updateParam.getId(), BlogEntity.class);
if (Objects.isNull(blogEntity)) {
return ApiResult.fail(ApiMsgEnum.BLOG_NOT_EXIST);
}
BeanUtil.copyProperties(updateParam, blogEntity, CopyOptions.create().ignoreError().ignoreNullValue());
elasticTemplate.save(blogEntity);
return ApiResult.success(blogEntity);
}
/**
* 删除单条
*
* @param deleteOneParam
* @return
*/
@Override
public ApiResult<Void> delete(BlogDeleteOneParam deleteOneParam) {
BlogEntity blogEntity = elasticTemplate.get(deleteOneParam.getId(), BlogEntity.class);
if (Objects.isNull(blogEntity)) {
return ApiResult.fail(ApiMsgEnum.BLOG_NOT_EXIST);
}
elasticTemplate.delete(deleteOneParam.getId(), BlogEntity.class);
return ApiResult.success();
}
}
./demo-elasticsearch/src/main/java/com/ljq/springboot/elasticsearch/service/impl/BlogServiceImpl.java
/**
* 分页查询
*
* @param queryPageParam
* @return
*/
@Override
public ApiResult<Page<BlogEntity>> queryPage(BlogQueryPageParam queryPageParam) {
Pageable pageable = PageRequest.of(queryPageParam.getCurrentPage() - 1, queryPageParam.getPageSize());
// 构建查询条件
NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder filter = QueryBuilders.boolQuery();
// id-精确查询-idsQuery
if (StrUtil.isNotBlank(queryPageParam.getId())) {
filter.must(QueryBuilders.idsQuery().addIds(queryPageParam.getId()));
}
// 标题-模糊查询-matchQuery
if (StrUtil.isNotBlank(queryPageParam.getTitle())) {
filter.must(QueryBuilders.matchQuery("title",
queryPageParam.getTitle()).operator(Operator.OR).fuzziness(Fuzziness.AUTO));
}
// 作者-精确查询-queryStringQuery
if (StrUtil.isNotBlank(queryPageParam.getAuthor())) {
filter.must(QueryBuilders.queryStringQuery(
queryPageParam.getAuthor()).defaultField("author").fuzziness(Fuzziness.AUTO));
}
// 内容-模糊查询-fuzzyQuery
if (StrUtil.isNotBlank(queryPageParam.getContent())) {
filter.must(QueryBuilders.fuzzyQuery("content",
queryPageParam.getContent().toLowerCase()).fuzziness(Fuzziness.AUTO));
}
// 全文查询-模糊查询-multiMatchQuery
if (StrUtil.isNotBlank(queryPageParam.getKeyword())) {
filter.must(QueryBuilders.multiMatchQuery(
queryPageParam.getKeyword(),"title", "author", "content").fuzziness(Fuzziness.AUTO));
}
// 客户端时间戳-范围查询-rangeQuery
if (Objects.nonNull(queryPageParam.getMinClientTimestamp())) {
filter.must(QueryBuilders.rangeQuery("clientTimestamp")
.gte(queryPageParam.getMinClientTimestamp()));
}
if (Objects.nonNull(queryPageParam.getMaxClientTimestamp())) {
filter.must(QueryBuilders.rangeQuery("clientTimestamp")
.lte(queryPageParam.getMaxClientTimestamp()));
}
searchQueryBuilder.withFilter(filter);
// 分页信息
searchQueryBuilder.withPageable(pageable);
NativeSearchQuery query = searchQueryBuilder.build();
SearchHits<BlogEntity> searchHits = elasticTemplate.search(query, BlogEntity.class);
log.info("搜索结果: {}\n{}", searchHits, JSONUtil.toJsonStr(searchHits.getSearchHits()));
Page<BlogEntity> page = PageableExecutionUtils.getPage(searchHits.getSearchHits().stream()
.map(SearchHit::getContent).collect(Collectors.toList()),pageable, searchHits::getTotalHits);
return ApiResult.success(page);
}
./demo-elasticsearch/src/main/java/com/ljq/springboot/elasticsearch/DemoElasticsearchApplication.java
package com.ljq.springboot.elasticsearch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.elasticsearch.config.EnableElasticsearchAuditing;
/**
* @author ls-ljq
*/
@EnableElasticsearchAuditing
@SpringBootApplication(scanBasePackages = "com.ljq.springboot.elasticsearch")
public class DemoElasticsearchApplication {
public static void main(String[] args) {
SpringApplication.run(DemoElasticsearchApplication.class, args);
}
}
SpringBoot elasticsearch 自动化配置类
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration
SpringBoot 2.5.7 :
支持的配置类 | 配置前缀 |
---|---|
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties |
spring.elasticsearch.rest |
SpringBoot 2.6.1 :
支持的配置类 | 是否过时 | 配置前缀 |
---|---|---|
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchProperties |
否 | spring.elasticsearch |
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties |
否 | spring.elasticsearch.rest |
org.springframework.boot.autoconfigure.elasticsearch.DeprecatedElasticsearchRestClientProperties |
是 | spring.elasticsearch.rest |
兼容性配置示例:
## spring
spring:
application:
name: demo-elasticsearch
elasticsearch:
rest:
uris: 192.168.1.110:9200
username: elastic
password: elastic666
connection-timeout: 10S
read-timeout: 30S
sniffer:
interval: 5m
delay-after-failure: 1m
termQuery fuzzyQuery 区分大小写,推荐操作方案: (1)在应用程序中将用户输入的搜索词统一转化为小写;(2) 在Elasticsearch 中添加转小写过滤器
spring-boot - 如何在 Elasticsearch 中搜索不匹配大小写的精确文本
matchQuery 只能匹配一个完整的单词,如果数据库中有 「spring」 这个单词,搜索条件为 「sprin」 则无法搜索到,而 fuzzyQuery 可以
elasticsearch 默认的中文分词是按照一个字一个字进行分词,英文分词则是按照一个完整的单词进行分词,如"黄鹤楼" 可以分出 "黄","鹤","楼"三个词;"springboot" 则就是一个单词
Spring Data Elasticsearch - Reference Documentation
Elasticsearch实战篇——Spring Boot整合ElasticSearch
Introduction to Spring Data Elasticsearch -- Baeldung
SpringBoot Elasticsearch 7.x 多条件分页查询
ElasticSearch集成SpringData史上最全查询教程
Elasticsearch Queries with Spring Data
commit 8eb190105ce772f29caf9430eaacede40582c8b1 (HEAD -> dev, origin/master, origin/dev, origin/HEAD, master)
Author: lujunqiang <flying9001@gmail.com>
Date: Tue Dec 14 18:58:45 2021 +0800
代码-新增 SpringBoot 2.5 集成 Elasticsearch 7 实现全文模糊查询
版本回退命令
git reset --soft 8eb190105ce772f29caf9430eaacede40582c8b1