从零开始的Elasticsearch学习-springboot整合Elasticsearch(非响应式)

从零开始的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的类中。

图1

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

映射注解及其作用:

  1. @Document
    • 作用:标记一个类为Elasticsearch文档。
    • 属性:
      • indexName:定义Elasticsearch中的索引名称。
      • createIndex:是否在启动时创建索引(默认为true)。
      • versionType:版本管理配置。 默认值为EXTERNAL。
      • shardsreplicas:定义索引的分片数和副本数。

示例:

@Document(indexName = "blog",createIndex = true, shards = 3, replicas = 2)
public class EsBlog {
    //...
    }
  1. @Id
    • 作用:该字段值为文档的ID。

示例:

	@Id
	private String id;
  1. @Field
    • 作用:定义Java对象字段与Elasticsearch文档字段之间的映射关系,包括字段的数据类型、是否建立索引、是否分词、存储策略等,将Java对象字段映射到文档字段,会添加注解里的信息,当然不设置也是有默认值的。
    • 属性:
      • type:定义字段的数据类型(如TextFieldTypeKeywordFieldType等)。
      • name:定义字段在Elasticsearch中的名称(默认为Java字段名)。
      • includeInParent:如果这是一个嵌套对象的一部分,是否包含在父对象中。
      • store:控制是否存储字段的原始值。如果设置为true,Elasticsearch会存储字段的原始值,这样在获取文档时可以直接获取到字段的值,而不需要重新从源数据中提取。这可能会增加索引的大小,但可以提高查询性能。
      • analyzer:用于指定字段的分词器。分词器用于将文本字段拆分成单独的词条,以便进行全文搜索。你可以使用Elasticsearch提供的内置分词器,也可以自定义分词器。
      • @Transient:默认情况下,所有字段在存储或检索时都映射到文档,此注释排除字段。

解释:store属性用来指定字段是否存储原始值。这个属性有两个可选值:truefalse

  • 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; //
  1. @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各参数:

  1. 第一个参数 new String[]{"content"} 指定了要在哪些字段上进行相似度查询。这里指定了 “content” 字段,意味着查询将基于 “content” 字段的内容来查找相似文档。
  2. 第二个参数 new String[]{text} 是要进行匹配的实际文本内容。这里使用了变量 text,代表将要传入的实际文本。
  3. 第三个参数传入 null,表示不指定一个文档列表来限制搜索范围。通常,这个参数用于提供一组文档,查询会基于这些文档的内容来查找相似的文档。
  4. minTermFreq(2) 设置了最小词频。只要词在至少两个文档中出现,就会被考虑。一个文档出现两次算
  5. 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 会根据这些模式自动构建查询。

常见的方法名组合及其对应的查询类型:

  1. findByXxx:用于执行基于字段的查询。例如,findByTitle 会构建一个查询,根据 title 字段的值来过滤文档。
  2. findAllByXxx:与 findByXxx 类似,但返回所有匹配的文档,而不仅仅是第一个。
  3. findTopNByXxx:根据某个字段的值返回前 N 个匹配的文档。
  4. countByXxx:返回匹配某个条件的文档数量。
  5. existsByXxx:检查是否存在匹配某个条件的文档。
  6. deleteByXxx:删除匹配某个条件的文档。

xxx为文档的字段

分页和排序:

  1. Page:结合Pageable对象使用,可以执行分页查询。

    findAllByTitle(String title, Pageable pageable);
    
  2. Sort:结合Sort对象使用,可以对查询结果进行排序。

    findAllByTitle(String title, Sort sort);
    

复杂的查询:

  1. AndOrNot:组合多个条件。

    findByTitleAndTitle(String title, String summary);
    findByTitleOrSummary(String title, String summary);
    findByTitleNot(String title);
    
  2. IsBetweenLessThanGreaterThan 等:用于特定条件的查询。

    findByAgeIs(int age);
    findByAgeBetween(int min, int max);
    findByPriceLessThan(double price);
    

​ 3.模糊查询:使用 LikeStartingWith 等后缀可以实现模糊查询:

// 模糊匹配 "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 对象并将分页参数应用到查询中,构成一个完整的查询语句。

全部demo代码

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值