从零开始的Elasticsearch学习-springboot整合Elasticsearch(非响应式)
一、前言
本文介绍springboot整合Elasticsearch相关内容。
官方文档和api手册:https://docs.spring.io/spring-data/elasticsearch/docs/
各位同学可以根据自己使用的spring-data-elasticsearch版本在官方文档学习可以快速上手
如果使用spring-boot-starter-data-elasticsearch依赖,在其子包可以看spring-data-elasticsearch版本
注意:由于spring-data-elasticsearch版本迭代太快,不同版本可能有所区别,比如ElasticsearchTemplate
在4.4.x版本被移除。
官网查看不同版本更改:https://docs.spring.io/spring-data/elasticsearch/reference/migration-guides.html
二、环境准备
2.1环境配置
本文演示使用的springboot版本:2.5.1(对应的spring-data-elasticsearch为4.2.1)
elasticsearch版本:7.5.0 elasticsearch安装教程
kibana版本:7.5.0
使用swagger或postman发送请求测试接口(本文使用swagger)
2.2版本对应关系
spring-data-elasticsearch和elasticsearch版本对应:官网查看
一般情况:
- Spring Data Elasticsearch 5.x 系列通常与 Elasticsearch 8.x 版本兼容。
- Spring Data Elasticsearch 4.x 系列通常与 Elasticsearch 7.x 版本兼容。
- Spring Data Elasticsearch 3.x 系列通常与 Elasticsearch 6.x 版本兼容。
- Spring Data Elasticsearch 2.x 系列通常与 Elasticsearch 5.x 版本兼容。
出现问题就按官网版本对应配置
三、Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
四、配置
4.1application.yml配置
spring:
elasticsearch:
rest:
uris: 服务器地址:9200 # Elasticsearch服务器的地址和端口
username: # Elasticsearch的用户名
password: # Elasticsearch的密码
注意:es username和password没有就可以不用写或空着,uris默认:http://Localhost:9200
使用集群的同学这样配置:
spring:
elasticsearch:
rest:
uris: node1_ip:9200,node2_ip:9200 ,node3_ip:9201
username: # Elasticsearch的用户名
password: # Elasticsearch的密码
uris:后面多个节点用逗号隔开
对于yml配置的参数或者前缀不知道的同学可以打开进入spring-boot-autoconfigure->org->springframework->boot->autoconfigure,找到对应包,一般在结尾Properties的类中。
4.2java代码配置
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.xj.demo.repository")
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("服务器地址:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
解释:1.@EnableElasticsearchRepositories(basePackages = “com.xj.demo.repository”) :启用Elasticsearch仓库,并指定仓库所在的包路径*
2.继承AbstractElasticsearchConfiguration类重写自定义配置,实现自定义RestHighLevelClient高级客户端。
3.创建一个ClientConfiguration对象写入配置,除了ip端口,还可以加其他配置,可以进入ClientConfiguration类查看
4.返回RestHighLevelClient对象
使用集群的同学可以这样配置:
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.xj.demo.repository")
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("node1_ip:9200","node2_ip:9200","node3_ip:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
connectedTo(String …s)允许配置多节点
4.3详细配置
对于有需要的同学可以对每个参数进行详细配置,模板如下:
// 创建一个ClientConfiguration对象,用于配置Elasticsearch客户端
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
// 配置Elasticsearch集群的节点地址,这里连接了两个节点:"localhost:9200"和"localhost:9291"
.connectedTo("localhost:9200", "localhost:9291")
// 启用SSL连接
.usingSsl()
// 配置代理服务器地址和端口
.withProxy("localhost:8888")
// 配置Elasticsearch的路径前缀,这里为"ela"
.withPathPrefix("ela")
// 设置连接超时时间,这里为5秒
.withConnectTimeout(Duration.ofSeconds(5))
// 设置套接字超时时间,这里为3秒
.withSocketTimeout(Duration.ofSeconds(3))
// 设置默认的请求头,这里使用之前创建的httpHeaders对象
.withDefaultHeaders(httpHeaders)
// 配置基本认证,需要用户名和密码
.withBasicAuth(username, password)
// 添加一个自定义的请求头,这个请求头的值会随着每次请求而动态变化
// 当前时间以ISO格式显示
.withHeaders(() -> {
HttpHeaders headers = new HttpHeaders();
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
// 配置WebClient,可以自定义WebClient的配置
.withWebClientConfigurer(webClient -> {
// ... 这里可以添加对WebClient的自定义配置
return webClient;
})
// 配置HttpClient,可以自定义HttpClient的配置
.withHttpClientConfigurer(clientBuilder -> {
// ... 这里可以添加对HttpClient的自定义配置
return clientBuilder;
})
// ... 这里可以添加其他配置选项
.build(); // 构建ClientConfiguration对象
注意:只能在响应式Elasticsearch客户端使用withHeaders(响应式客户端在另一篇介绍),withHeaders内不能引入任何阻塞操作,上面时间操作本身是非常快的,并且不涉及任何IO操作或外部资源访问,因此不太可能引起阻塞。阻塞的操作(例如从数据库或远程服务获取数据)。
五、Elasticsearch客户端
5.1.传输客户端TransportClient(不建议使用)
TransportClient
在Elasticsearch 7中被弃用,并将在Elasticsearch 8中被删除。Spring Data Elasticsearch4.0版本开始弃用它的类。
看看它的配置:
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
return client;
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(refreshPolicy());
return template;
}
}
注意:通过代码我们可以知道TransportClient必须需要设置集群名称。
5.2High Level REST Client(建议使用)
High Level REST Client是TransportClient的代替,High Level REST Client的配置在第四节。
@Autowired
RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient();
可以使用highLevelClient获取低级客户端
5.3日志输出
如果想要捕获Spring Data Elasticsearch客户端的底层传输日志(请求和响应的详细信息)。
可以加上
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/>
例如配置在日志框架(如Logback或Log4j2)。
Logback:输出到日志文件
<configuration>
<!-- 定义文件appender -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>elasticsearch-wire-logs.log</file> <!-- 日志文件名 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 配置org.springframework.data.elasticsearch.client.WIRE的logger -->
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="TRACE" additivity="false">
<appender-ref ref="FILE" /> <!-- 引用上面定义的FILE appender -->
</logger>
<!-- 根logger,这里不输出到文件 -->
<root level="INFO">
<!-- 可以添加其他appender,例如控制台appender -->
</root>
</configuration>
Log4j2:输出到控制台
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
六、对象映射
6.1映射注解
下面所有的代码演示都是基于博客实体类EsBlog
映射注解及其作用:
@Document
:- 作用:标记一个类为Elasticsearch文档。
- 属性:
indexName
:定义Elasticsearch中的索引名称。createIndex
:是否在启动时创建索引(默认为true)。versionType
:版本管理配置。 默认值为EXTERNAL。shards
和replicas
:定义索引的分片数和副本数。
示例:
@Document(indexName = "blog",createIndex = true, shards = 3, replicas = 2)
public class EsBlog {
//...
}
@Id
:- 作用:该字段值为文档的ID。
示例:
@Id
private String id;
@Field
:- 作用:定义Java对象字段与Elasticsearch文档字段之间的映射关系,包括字段的数据类型、是否建立索引、是否分词、存储策略等,将Java对象字段映射到文档字段,会添加注解里的信息,当然不设置也是有默认值的。
- 属性:
type
:定义字段的数据类型(如TextFieldType
、KeywordFieldType
等)。name
:定义字段在Elasticsearch中的名称(默认为Java字段名)。includeInParent
:如果这是一个嵌套对象的一部分,是否包含在父对象中。store
:控制是否存储字段的原始值。如果设置为true
,Elasticsearch会存储字段的原始值,这样在获取文档时可以直接获取到字段的值,而不需要重新从源数据中提取。这可能会增加索引的大小,但可以提高查询性能。analyzer
:用于指定字段的分词器。分词器用于将文本字段拆分成单独的词条,以便进行全文搜索。你可以使用Elasticsearch提供的内置分词器,也可以自定义分词器。@Transient
:默认情况下,所有字段在存储或检索时都映射到文档,此注释排除字段。
解释:
store
属性用来指定字段是否存储原始值。这个属性有两个可选值:true
和false
。
- 当
store
设置为false
时(这是默认设置),字段的值只会被存储在_source
字段中。_source
字段是一个特殊的字段,它存储了文档的原始JSON表示。当你检索文档时,你可以从_source
字段中提取字段的值。但是,如果你只需要检索某个字段的值而不是整个文档,那么从_source
字段中提取可能会涉及更多的解析和计算工作。- 当
store
设置为true
时,字段的值会存储在一个与_source
平级的独立Field域中,同时也会存储在_source
中。这意味着该字段的值有两个拷贝:一个在_source
字段中,另一个在专门的Field域中。这种设置的好处是,当你只需要检索该字段的值时,可以直接从专门的Field域中获取,而不需要解析整个_source
字段,从而提高了查询性能。但是,需要注意的是,将字段设置为store=true
会占用额外的磁盘空间,因为每个字段的值都需要被存储两份。
includeInParent
属性可以简单理解为,比如我的esblog博客里面有个字段tags,类型为List,它也有自己的属性id,name啥的,@Field(includeInParent = true)加到tags属性上面,tags内的字段会存储_source中。
示例:
//博客类型
@Field(name = "type" type = FieldType.Keyword)//FieldType.Keyword表示关键词类型,适用于结构化搜索,代表该字段是一个整体,不能分词
private Integer type;
//博客标题
@Field(type = FieldType.Text,store=true)//store=true表示该字段值不仅在存储在`_source`字段,还存了一份专门的Field域中
private String title;
//博客简介
@Field(type = FieldType.Text,analyzer = "ik_max_word")//FieldType.Text表示文本类型,适用于全文搜索,analyzer = "ik_max_word"指定分词器为ik
//标签
private String summary;
@Field(type = FieldType.Nested, includeInParent = true)//type = FieldType.Nested表示这个字段包含了一个嵌套文档的类型,includeInParent = true表示嵌套文档的字段值将包含在父文档的_source字段中。
private List<Tag> tags; //
@GeoPoint
:- 作用:标记一个字段为地理点字段,用于存储地理坐标。
6.2映射关系
普通字段映射
@TypeAlias("myBlog")
@Document(indexName = "blog")
public class EsBlog {
@Id
private String id;
//...
}
{
"_class" : "com.xj.demo.EsBlog",
"id" : "cb7bef",
"firstname" : "Sarah",
"lastname" : "Connor"
}
_class记录数据实体类型信息,可以用来Type Hints,类型提示作用。
@TypeAlias
注释:自定义类型提示
"_class" : "myBlog"
Maps映射
支持map(key,value),但Map键需要String类型才能被Elasticsearch处理。
public class Person {
// ...
Map<String, Address> knownLocations;
}
{
// ...
"knownLocations" : {
"arrivedAt" : {
"city" : "Los Angeles",
"street" : "2800 East Observatory Road",
"location" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
}
}
七、使用ElasticsearchRestTemplate操作
7.1基本概念
在使用前我们先理清ElasticsearchTemplate,ElasticsearchRestTemplate和ElasticsearchOperations
ElasticsearchOperations:代表了与Elasticsearch进行交互的一系列操作的父类接口,高级客户端和TransportClient都有ElasticsearchOperations接口及其实现类。
**ElasticsearchTemplate:**TransportClient客户端ElasticsearchOperations接口的实现类,4.0版本弃用。
ElasticsearchRestTemplate:High Level REST Client高级客户端的ElasticsearchOperations接口的实现类。
ElasticsearchRestTemplate bean
public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
public AbstractElasticsearchConfiguration() {
}
@Bean
public abstract RestHighLevelClient elasticsearchClient();
@Bean(
name = {"elasticsearchOperations", "elasticsearchTemplate"}
)
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter, RestHighLevelClient elasticsearchClient) {
ElasticsearchRestTemplate template = new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(this.refreshPolicy());
return template;
}
}
可以看出如果根据bean名导入对象elasticsearchOperations和elasticsearchTemplate都可以
@Resouece
private final ElasticsearchOperations elasticsearchOperations或elasticsearchTemplate;
7.2实体类
EsBlog
@Data
@TypeAlias("blog")
@Document(indexName = "blog")
public class EsBlog {
@Id
private String id;
/**
* 作者id
*/
@Field(type = FieldType.Keyword)
private Author authorId;
/**
* 标签
*/
@Field(type = FieldType.Nested, includeInParent = true)
private List<Tag> tags;
/**
* 博客类型
*/
@Field(type = FieldType.Keyword)
private Integer type;
/**
* 博客标题
*/
@Field(type = FieldType.Keyword)
private String title;
/**
* 博客简介
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String summary;
/**
* 博客内容
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String content;
}
Author
@Data
public class Author {
private String id;
private String name;
}
Tag
@Data
public class Tag {
private String name;
private String color;
}
7.3基本增删改操作
private final ElasticsearchOperations elasticsearchOperations;
@Autowired
public EsBlogController(ElasticsearchOperations elasticsearchOperations) {
this.elasticsearchOperations = elasticsearchOperations;
}
保存博客
@PostMapping("/saveBlog")
@ApiOperation(value = "保存博客", notes = "保存博客")
public String save(@RequestBody EsBlog esBlog) {
EsBlog blog = elasticsearchOperations.save(esBlog, IndexCoordinates.of("blog"));
return "success";
}
IndexCoordinates.of(“blog”):在索引blog操作
批量保存
@PostMapping("/saveAllBlog")
@ApiOperation(value = "批量保存博客", notes = "批量保存博客")
public String saveAll(@RequestBody List<EsBlog> list) {
// 将博客list转换为IndexQuerylist,每个IndexQuery代表一个待索引的文档
List<IndexQuery> indexQueries = list.stream()
.map(esBlog -> new IndexQueryBuilder()
.withId(esBlog.getId().toString())
.withObject(esBlog)
.build())
.collect(Collectors.toList());
// 使用ElasticsearchOperations的bulkIndex方法批量索引文档到名为"blog"的索引中
elasticsearchOperations.bulkIndex(indexQueries, IndexCoordinates.of("blog"));
return "success";
}
根据id删
@GetMapping("/deleteBlogById")
@ApiOperation(value = "通过id删除博客", notes = "通过id删除博客")
public String deleteBlogById(String id) {
return elasticsearchOperations.delete(id, IndexCoordinates.of("blog"));
}
7.4StringQuery查询
StringQuery可以自定义json格式的查询,例:
在content字段中匹配包含"三"这个字的文档
@GetMapping("/StringQuery")
@ApiOperation(value = "StringQuery", notes = "StringQuery")
public List<EsBlog> StringQuery() {
//自定义json格式的查询
Query query = new StringQuery("{ \"match\": { \"content\": { \"query\": \"三\" } } } ");
SearchHits<EsBlog> searchHits = elasticsearchOperations.search(query, EsBlog.class);
//返回所有匹配的EsBlog对象
return searchHits.getSearchHits().stream()
.map(hit -> hit.getContent())
.collect(Collectors.toList());
}
注意:searchHits是Elasticsearch查询的结果的封装类,包含了与查询匹配的数据以及一些元数据(如得分、索引、类型等),上面只需要对象数据,就使用getContent()方法
7.5NativeSearchQuery查询
使用NativeSearchQuery可以构建复杂的查询,包括过滤、排序、分页等,并直接与 Elasticsearch 的查询 DSL(领域特定语言)交互。
通过关键字搜索博客
@GetMapping("/searchBlogByKey")
@ApiOperation(value = "通过关键字搜索博客", notes = "通过关键字搜索博客")
public SearchHits<EsBlog> searchBlogByKey(@RequestParam(required = false) String keywords) {
//构建NativeSearchQuery查询
Query searchQuery = new NativeSearchQueryBuilder().
withQuery(QueryBuilders.multiMatchQuery(keywords, "title", "content", "summary"))
.build();
//执行查询,获取结果
SearchHits<EsBlog> search = elasticsearchOperations.search(searchQuery, EsBlog.class);
return search;
}
解释:multiMatchQuery(keywords, “title”, “content”, “summary”))->使用multiMatchQuery来构建一个多字段匹配查询,该查询会在"title", “content”, "summary"这三个字段中 搜索与keywords匹配的内容。multiMatchQuery允许你在多个字段上执行相同的查询字符串, Elasticsearch会根据字段的相关性等因素来决定哪个字段的匹配度更高。
除了链式,也可以用构造器的方式构建
public NativeSearchQuery(@Nullable QueryBuilder query, @Nullable QueryBuilder filter, @Nullable List<SortBuilder<?>> sorts, @Nullable HighlightBuilder highlightBuilder, @Nullable Field[] highlightFields) {
this.query = query;
this.filter = filter;
this.sorts = sorts;
this.highlightBuilder = highlightBuilder;
this.highlightFields = highlightFields;
}
例子都是链式的
查询所有
@GetMapping("/findAllBlogs")
@ApiOperation(value = "查询所有博客", notes = "查询所有博客")
public List<EsBlog> findAll() {
SearchHits<EsBlog> search = elasticsearchOperations.search(new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchAllQuery()).build(), EsBlog.class);
List<EsBlog> list = search.getSearchHits().stream().map(a -> a.getContent()).collect(Collectors.toList());
return list;
}
解释:matchAllQuery()->匹配所有文档的查询。
分页显示
@GetMapping("/searchBlogPage")
@ApiOperation(value = "查询所有博客分页显示", notes = "查询所有博客分页显示")
public String searchBlogPage(@RequestParam(name = "currentPage", required = false, defaultValue = "1") Integer
currentPage,
@RequestParam(name = "pageSize", required = false, defaultValue = "10") Integer
pageSize) {
Pageable pageable = PageRequest.of(currentPage - 1, pageSize);
//构建查询
Query searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchAllQuery())
.withPageable(pageable)
.build();
//执行查询,获取结果
SearchHits<EsBlog> search = elasticsearchOperations.search(searchQuery, EsBlog.class);
return search.getSearchHits().toString();
}
解释:currentPage->页码,不填默认1,pageSize->每页数量,不填默认10
pageable->创建一个Pageable对象,用于分页查询
withPageable(pageable)->分页设置导入
高亮显示关键字
@PostMapping("/queryWithHighlight")
@ApiOperation(value = "通过关键字搜索博客内容且高亮显示关键字", notes = "通过关键字搜索博客内容且高亮显示关键字")
public SearchHits<EsBlog> queryWithHighlight(@RequestParam(required = false) String keywords) {
HighlightBuilder highlightBuilder = new HighlightBuilder().field("content")
.preTags("<em style='color:red'>")//关键字前缀
.postTags("</strong>")//关键字后缀
.fragmentSize(200) // 高亮片段的大小
.numOfFragments(3);// 返回的高亮片段数量;
//构建查询
Query searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keywords, "content"))
.withHighlightBuilder(highlightBuilder)
.build();
//执行查询,获取结果
SearchHits<EsBlog> search = elasticsearchOperations.search(searchQuery, EsBlog.class);
return search;
}
highlightBuilder->创建一个highlightBuilder对象,用于查询结果关键字高亮显示
withHighlightBuilder(highlightBuilder)->导入高亮配置
聚合查询
@GetMapping("/searchCountTermType")
@ApiOperation(value = "聚合查询", notes = "聚合查询")
public List<? extends Bucket> searchCountTermType() {
//构建查询,执行查询
Query searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchAllQuery())
.addAggregation(AggregationBuilders.terms("type")//聚合名
.field("type.keyword")// 聚合的字段
.size(10))// 返回的桶的最大数量
.build();
//执行查询,获取结果
SearchHits<EsBlog> search = elasticsearchOperations.search(searchQuery, EsBlog.class);
// 获取聚合结果
Terms types = search.getAggregations().get("type");
List<? extends Bucket> buckets = types.getBuckets();
// 处理聚合结果
for (Terms.Bucket bucket : buckets) {
String key = bucket.getKeyAsString(); // 聚合的键(即type字段的值)
long docCount = bucket.getDocCount(); // 文档计数
System.out.println("Type: " + key + ", Count: " + docCount);
}
return buckets;
}
解释:AggregationBuilders.terms(“type”)…->构建聚合查询,聚合名type,字段type,限制桶的数量,这里的桶就是分的组,连起来就是将查询结果根据type分组,前10个最常见的type值会被返回
返回结果docCount显示每一组的数量
根据内容搜索相似文档的查询
@GetMapping("/searchMoreLikeThis")
@ApiOperation(value = "根据内容搜索相似文档的查询", notes = "根据内容搜索相似文档的查询")
public List<EsBlog> searchMoreLikeThis(String text) {
//构建查询
MoreLikeThisQueryBuilder queryBuilder = new MoreLikeThisQueryBuilder(new String[]{"content"}, new String[]{text}, null)
.minTermFreq(2)
.minDocFreq(2);
Query searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
.build();
//执行查询,获取结果
SearchHits<EsBlog> searchHits = elasticsearchOperations.search(searchQuery, EsBlog.class);
return searchHits.getSearchHits().stream()
.map(hit -> {
EsBlog content = hit.getContent();
//...
return content;
})
.collect(Collectors.toList());
}
}
解释:queryBuilderQuery->构建MoreLikeThisQuery查询条件对象,MoreLikeThisQueryBuilder各参数:
- 第一个参数
new String[]{"content"}
指定了要在哪些字段上进行相似度查询。这里指定了 “content” 字段,意味着查询将基于 “content” 字段的内容来查找相似文档。- 第二个参数
new String[]{text}
是要进行匹配的实际文本内容。这里使用了变量text
,代表将要传入的实际文本。- 第三个参数传入
null
,表示不指定一个文档列表来限制搜索范围。通常,这个参数用于提供一组文档,查询会基于这些文档的内容来查找相似的文档。minTermFreq(2)
设置了最小词频。只要词在至少两个文档中出现,就会被考虑。一个文档出现两次算minDocFreq(2)
设置了最小文档频率。要词至少出现在两个文档中,就会被考虑。也就是要出现在不同文档中,一个文档出现两次不算withQuery(queryBuilder)->导入查询条件
适用场景:用MLT查询找到与该文章主题或内容相似的其他文章,或者购物网站相似的商品,推荐给用户。
八、Elasticsearch Repositories
Elasticsearch Repositories使用了 Spring Data Repositories中的概念和方法,如 CRUD 操作、分页查询、排序。
三个主要接口:
- CrudRepository:这是一个基本的响应式仓库接口,提供了一些基本的操作,如保存、删除和查询实体。
- PagingAndSortingRepository这个接口继承自
CrudRepository
,添加了额外的排序和分页功能。 - ElasticsearchRepository:这个接口在 继承自
PagingAndSortingRepository
在其基础上基础上添加searchSimilar,相似文档查询的方法。
可以自定义接口继承这三个接口,使用接口中的默认方法或自定义方法查询。
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog, String> {
@Query("{\"match\": {\"title\": {\"query\": \"?0\"}}}")
Page<EsBlog> findByTitle(String title, Pageable pageable);
}
8.1 使用仓库接口提供的方法
简单的增删改查,直接给代码:
@RestController()
@RequestMapping("/repository")
@Api(value = "repository", tags = "repository")
public class EsBlog_RepositoryController {
private final EsBlogRepository repository;
public EsBlog_RepositoryController(EsBlogRepository repository) {
this.repository = repository;
}
@PostMapping("/saveBlog")
@ApiOperation(value = "保存博客", notes = "保存博客")
public EsBlog save(@RequestBody EsBlog esBlog) {
return repository.save(esBlog);
}
@PostMapping("/saveAllBlog")
@ApiOperation(value = "批量保存博客", notes = "批量保存博客")
public void saveAll(@RequestBody List<EsBlog> list) {
repository.saveAll(list);
}
@GetMapping("/deleteBlogById")
@ApiOperation(value = "通过id删除博客", notes = "通过id删除博客")
public void deleteBlogById(String id) {
repository.deleteById(id);
}
@PostMapping("/deleteAllBlog")
@ApiOperation(value = "批量删除博客", notes = "批量删除博客")
public void deleteAll(@RequestBody List<EsBlog> list) {
repository.deleteAll(list);
}
@GetMapping("/searchById/{id}")
@ApiOperation(value = "通过id查询", notes = "通过id查询")
public ResponseEntity<EsBlog> searchById(@PathVariable String id) {
Optional<EsBlog> optional = repository.findById(id);
return optional.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping("/searchByIds")
@ApiOperation(value = "通过id批量查询", notes = "通过id批量查询")
public List<EsBlog> searchAll(@RequestBody List<String> ids) {
return (List<EsBlog>)repository.findAllById(ids);
}
// 查询所有,并根据id倒序排序
@GetMapping("/findAll")
@ApiOperation(value = "查询所有", notes = "查询所有")
public List<EsBlog> findAll() {
return (List<EsBlog>)repository.findAll(Sort.by("title").descending());
}
}
8.2 方法名的组合来定义查询
当你定义一个仓库接口并使用特定的方法名模式时,Spring Data 会根据这些模式自动构建查询。
常见的方法名组合及其对应的查询类型:
- findByXxx:用于执行基于字段的查询。例如,
findByTitle
会构建一个查询,根据title
字段的值来过滤文档。 - findAllByXxx:与
findByXxx
类似,但返回所有匹配的文档,而不仅仅是第一个。 - findTopNByXxx:根据某个字段的值返回前 N 个匹配的文档。
- countByXxx:返回匹配某个条件的文档数量。
- existsByXxx:检查是否存在匹配某个条件的文档。
- deleteByXxx:删除匹配某个条件的文档。
xxx为文档的字段
分页和排序:
-
Page:结合Pageable对象使用,可以执行分页查询。
findAllByTitle(String title, Pageable pageable);
-
Sort:结合Sort对象使用,可以对查询结果进行排序。
findAllByTitle(String title, Sort sort);
复杂的查询:
-
And、Or、Not:组合多个条件。
findByTitleAndTitle(String title, String summary); findByTitleOrSummary(String title, String summary); findByTitleNot(String title);
-
Is、Between、LessThan、GreaterThan 等:用于特定条件的查询。
findByAgeIs(int age); findByAgeBetween(int min, int max); findByPriceLessThan(double price);
3.模糊查询:使用 Like
或 StartingWith
等后缀可以实现模糊查询:
// 模糊匹配 "title" 字段,类似于 SQL LIKE '%keyword%'
Entity findByTitleLike(String keyword);
// 匹配 "title" 字段开始的关键字,类似于 SQL LIKE 'keyword%'
Entity findByTitleStartingWith(String keyword);
这些方法的实现是由 Spring Data Elasticsearch 在运行时动态生成的,基于你定义的方法名和 Elasticsearch 的查询 DSL。
@Query自定义 JSON查询:
例如:
@Query("{\"match\": {\"title\": {\"query\": \"?0\"}}}")
Page<EsBlog> findByTitle(String title, Pageable pageable);
?0:占位符,实现动态绑定,?0代表第一个参数,?1代表第二个参数。
@Query
注解中的 JSON 将会定义查询的主体,框架会处理 Pageable
对象并将分页参数应用到查询中,构成一个完整的查询语句。