Spring Boot - 数据库集成06 - 集成ElasticSearch

news2025/2/3 2:14:28

Spring boot 集成 ElasticSearch

文章目录

  • Spring boot 集成 ElasticSearch
    • 一:前置工作
      • 1:项目搭建和依赖导入
      • 2:客户端连接相关构建
      • 3:实体类相关注解配置说明
    • 二:客户端client相关操作说明
      • 1:检索流程
        • 1.1:构建请求request和源source
        • 1.2:查询建造器queryBuilder构建
        • 1.3:请求建造器加入到请求源,请求源加入到请求中
        • 1.4:结果hits和highlight分析
      • 2:各种查询构造器说明
        • 2.1:MatchQueryBuilder
        • 2.2:RegexQueryBuilder
        • 2.3:IdsQueryBuilder
        • 2.4:MatchPhraseQueryBuilder
        • 2.5:MatchPhrasePrefixQueryBuilder
        • 2.6:MultiMatchQueryBuilder
        • 2.7:TermQueryBuilder
        • 2.8:FuzzyQueryBuilder
        • 2.9:RangeQueryBuilder
        • 2.10:WildcardQueryBuilder
        • 2.11:BoolQueryBuilder
        • 2.12:其他的QueryBuilder
      • 3:聚合建造器说明
        • 3.1:构造聚合条件
        • 3.2:将聚合条件交给builder,builder交给源,封装request
        • 3.3:结果解析和处理
    • 三:repo & template
      • 1:ElasticsearchRepository
        • 1.1:repo介绍
        • 1.2:自定义方法命名规范
      • 2:ElasticsearchRestTemplate
        • 2.1:template介绍
        • 2.2:template查询操作
          • 2.2.1:入参特殊情况处理【包括异常入参】
          • 2.2.2:构建查询条件Criteria
          • 2.2.3:构建高亮条件
          • 2.2.4:查询(也可能是其他的方法,不一定是search)
          • 2.2.5:结果封装
        • 2.3:创建索引和删除索引
      • 3:使用实例

在这里插入图片描述
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用 JSON作为文档序列化的格式

{
    "name" :     "John",
    "sex" :      "Male",
    "age" :      25,
    "birthDate": "1990/05/01",
    "about" :    "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

ES和传统的关系型数据库相关术语对比如下:

关系型数据库ES
数据库(Database)索引(index)
表(table)类型(Type)[es6.0.0废弃]
行(row)文档(document)
列(column)字段(field)
表结构(schema)映射(mapping)
索引反向索引
SQL查询DSL
Select * from tableGet http://…
update table set…Put http://…
deleteDelete http://…

一:前置工作

1:项目搭建和依赖导入

  • spring data -> spring-boot-starter-data-elasticsearch -> repo & template
  • transport & elasticsearch-rest-high-level-client -> client
<dependencies>
    <!-- spring boot启动器 -->
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- spring boot web启动器 -->
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- spring boot 测试启动器 -->
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!-- commons-lang3工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>

    <!-- spring data elasticsearch -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

    <!-- es 高阶客户端 -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>

    <!-- es 低阶客户端 -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>transport</artifactId>
    </dependency>

</dependencies>
spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200,http://localhost:9201,http://localhost:9202 # 集群地址
      connection-timeout: 5s # 连接超时时间
      read-timeout: 30s # 读取超时时间
      max-connections: 100 # 最大连接数
      max-connections-per-route: 20 # 每个路由的最大连接数
      password: es_password

2:客户端连接相关构建

package com.cui.es_demo.config;

import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;

import java.util.Arrays;

/**
 * @author cui haida
 * 2025/1/29
 */

@Configuration
@ConfigurationProperties(prefix = "spring.elasticsearch.rest")
public class ElasticSearchClientConfig {
    /**
     * 集群地址
     */
    private String uris;

    /**
     * 用户名和密码
     */
    private String username;
    private String password;

    @Bean
    public RestHighLevelClient restHighLevelClient() {

        // 创建连接, 指定url和用户名密码
        ClientConfiguration build = ClientConfiguration.builder()
                .connectedTo(uris)
                .withBasicAuth(username, password)
                .build();

        return RestClients.create(build).rest();
    }
}

3:实体类相关注解配置说明

package com.cui.es_demo.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.time.LocalDateTime;

/**
 * 实体类相关注解介绍
 * - @Document:指定索引库的名称,以及分片数、副本数、刷新间隔、是否允许创建索引
 * - @Id:指定主键
 * - @Field:指定字段的名称,以及字段的分词器,以及字段的存储类型,以及字段的分词器
 * - @JsonFormat:指定日期格式(注意日期类型字段不要用java.util.Date类型,要用java.time.LocalDate或java.time.LocalDateTime类型)
 * @author cui haida
 * 2025/1/30
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
// 指定对应的索引名称,, 主分片数 = 3, 副本数 = 1, 刷新间隔 = 1s, 允许创建索引
@Document(indexName = "person", shards = 3, replicas = 1, refreshInterval = "1s", createIndex = true)
public class Person {
    @Id
    private String id;

    @Field(type = FieldType.Keyword)
    private String name;

    @Field(type = FieldType.Keyword)
    private String age;

    // text类型,并使用IK最粗粒度的分词器
    // 检索时的分词器采用的是最细粒度的IK分词器
    @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")
    private String address;

    // 注意日期格式的特殊处理
    // 指定格式化和时区
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
}

二:客户端client相关操作说明

对索引相关的操作都不推荐在这里执行,建议直接使用Kibana

文档的更新和插入操作建议使用repo & template方式

这里只介绍查询相关操作

1:检索流程

1.1:构建请求request和源source
// 声明查询请求对象
SearchRequest client = new SearchRequest();
// 指定使用的索引(数据库)
client.indices(index);

// 声明searchSourceBuilder源对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

// ======================== 源对象其他常见设置 ===========================
// 1. 排序和浅显分页
// from  & size 默认都是-1,也就是有多少显示多少
// from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
searchSourceBuilder.from(0);
searchSourceBuilder.size(9999);

// 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
searchSourceBuilder
    .sort("age", SortOrder.DESC)
    .sort("name", SortOrder.ASC);
1.2:查询建造器queryBuilder构建
// =============== 以boolean为例 ==================
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
    .must(QueryBuilders.termQuery("name.keyword", "王五"))
    .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
    // 可以进行bool嵌套
    .must(QueryBuilders.boolQuery()
          .must(QueryBuilders.matchQuery("name", "张三"))
         )
    .boost(2.0f);
// 加入对应的builder到searchSourceBuilder中
searchSourceBuilder.query(boolQueryBuilder);


// ============ 如果有高亮设置 ===============
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<font style='color: red'>");
highlightBuilder.postTags("</font>");
highlightBuilder.field("name").field("age");
//同一字段中存在多个高亮值 设置都高亮
highlightBuilder.requireFieldMatch(true);

// 高亮属性中加入高亮配置器
searchSourceBuilder.highlighter(highlightBuilder);
1.3:请求建造器加入到请求源,请求源加入到请求中
searchSourceBuilder.query(xxxQueryBuilder);
// 封装request,指定要request的索引index,指定source -> searchSourceBuilder
SearchRequest request = new SearchRequest(index).source(searchSourceBuilder);
// 通过client.search获取响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
1.4:结果hits和highlight分析
List<Map<String, Object>> list = new ArrayList<>();
SearchResponse searchResponse = this.client.search(client, RequestOptions.DEFAULT);
if (searchResponse == null) {
    log.warn("没有返回值");
    return;
}


// todo: 然后从这个searchResponse解析各种的值 -> 根据返回结构
int failedShards = searchResponse.getFailedShards();
System.out.println("失败的分片数:" + failedShards);
int successfulShards = searchResponse.getSuccessfulShards();
System.out.println("成功的分片数:" + successfulShards);
RestStatus status = searchResponse.status();
System.out.println("状态:" + status);

//解析高亮数据
SearchHits hits = searchResponse.getHits();
System.out.println(hits.getMaxScore());
for (SearchHit hit : hits) {
    System.out.println("fields: " + hit.getFields());
    System.out.println("index is:" + hit.getIndex());
    System.out.println("document field is: " + hit.getDocumentFields());
    System.out.println("metadata field is: " + hit.getMetadataFields());
    System.out.println("score is: " + hit.getScore());
    System.out.println("-----------------");
    //原始数据,不包含高亮的数据
    Map<String, Object> sourceMap = hit.getSourceAsMap();
    //高亮数据,拿到高亮字段name
    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    HighlightField highlightTitle = highlightFields.get("name");
    //将原始数据的name替换
    if (highlightTitle != null) {
        Text[] fragments = highlightTitle.getFragments();
        if (fragments != null && fragments.length > 0) {
            sourceMap.replace("name", fragments[0].toString());
        }
    }
    list.add(sourceMap);//循环将数据添加入列表
}
list.forEach(System.out::println);
package com.cui.es_demo.client;

import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 客户端相关操作
 *
 * @author cui haida
 * 2025/1/30
 */
@Service
@Slf4j
public class ClientDemo {

    // 
    private final RestHighLevelClient client;

    public ClientDemo(RestHighLevelClient restHighLevelClient) {
        this.client = restHighLevelClient;
    }

    /**
     * 主要介绍客户端查询操作
     */
    public void searchTest(String[] args) {
        // 声明查询请求,同时声明作用的索引(数据库)
        SearchRequest request = new SearchRequest();
        request.indices("person");

        // 声明searchSourceBuilder源对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // ======================== 源对象其他常见设置 ===========================
        // 1. 排序和浅显分页
        // from  & size 默认都是-1,也就是有多少显示多少
        // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(9999);

        // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
        searchSourceBuilder
                .sort("age", SortOrder.DESC)
                .sort("name", SortOrder.ASC);


        // 构造查询建造器,根据不同的业务需求,进行不同的查询
        // =============== 以boolean为例 ==================
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("name.keyword", "王五"))
                .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
                // 可以进行bool嵌套
                .must(QueryBuilders.boolQuery()
                        .must(QueryBuilders.matchQuery("name", "张三"))
                )
                .boost(2.0f);
        // 请求构造器加入源中
        searchSourceBuilder.query(boolQueryBuilder);


        // ============ 如果有高亮设置 ===============
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font style='color: red'>");
        highlightBuilder.postTags("</font>");
        highlightBuilder.field("name").field("age");
        //同一字段中存在多个高亮值 设置都高亮
        highlightBuilder.requireFieldMatch(true);
        // 高亮属性中加入源中
        searchSourceBuilder.highlighter(highlightBuilder);

        // 进行查询
        // 1. 设置查询源(source -> request)
        request.source(searchSourceBuilder);
        // 2. 通过search方法进行查询
        List<Map<String, Object>> list = new ArrayList<>();
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            // 结果处理
            System.out.println("response.getHits().getTotalHits() = " + response.getHits().getTotalHits());


            // 然后从这个searchResponse解析各种的值 -> 根据返回结构
            int failedShards = response.getFailedShards();
            System.out.println("失败的分片数:" + failedShards);
            int successfulShards = response.getSuccessfulShards();
            System.out.println("成功的分片数:" + successfulShards);
            RestStatus status = response.status();
            System.out.println("状态:" + status);

            // 解析高亮数据
            SearchHits hits = response.getHits();
            System.out.println(hits.getMaxScore());
            for (SearchHit hit : hits) {
                System.out.println("fields: " + hit.getFields());
                System.out.println("index is:" + hit.getIndex());
                System.out.println("document field is: " + hit.getDocumentFields());
                System.out.println("metadata field is: " + hit.getMetadataFields());
                System.out.println("score is: " + hit.getScore());
                System.out.println("-----------------");
                // 原始数据,不包含高亮的数据
                Map<String, Object> sourceMap = hit.getSourceAsMap();
                // 高亮数据,拿到高亮字段name
                Map<String, org.elasticsearch.search.fetch.subphase.highlight.HighlightField> highlightFields = hit.getHighlightFields();
                HighlightField highlightTitle = highlightFields.get("name");
                // 将原始数据的name替换
                if (highlightTitle != null) {
                    Text[] fragments = highlightTitle.getFragments();
                    if (fragments != null && fragments.length > 0) {
                        sourceMap.replace("name", fragments[0].toString());
                    }
                }
                // 循环将数据添加入列表
                list.add(sourceMap);
            }
            list.forEach(System.out::println);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2:各种查询构造器说明

2.1:MatchQueryBuilder
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "李四")
    // 李和四必须都出现在name字段中才可以, operator可以是OR
    .operator(Operator.AND)
    // 匹配度必须是 >= 75%才行
    .minimumShouldMatch("75%")
    // 是否忽略数据类型转换异常
    .lenient(true);
2.2:RegexQueryBuilder
// 张姓开头的doc
RegexpQueryBuilder regexpQueryBuilder = 
    QueryBuilders.regexpQuery("name", "张*").caseInsensitive(true);
2.3:IdsQueryBuilder
String[] ids = new String[]{"1", "2", "3"};
IdsQueryBuilder idsQueryBuilder = 
    QueryBuilders.idsQuery().addIds(ids);
2.4:MatchPhraseQueryBuilder
MatchPhraseQueryBuilder match = 
    QueryBuilders.matchPhraseQuery("name", "李四").slop(2);
2.5:MatchPhrasePrefixQueryBuilder
MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder = 
    QueryBuilders.matchPhrasePrefixQuery("name", "张").maxExpansions(1);
2.6:MultiMatchQueryBuilder
String[] fieldNames = new String[]{"name", "age"};
String searchText = "老坛";
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(searchText, fieldNames)
    // 最多数量匹配
    .type(MultiMatchQueryBuilder.Type.MOST_FIELDS)
    // 使用and操作符和minimum_should_match参数来减少相关度低的文档数量
    .operator(Operator.AND);
2.7:TermQueryBuilder
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name.keyword", "李四");
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name.keyword", "李四光", "李四");
2.8:FuzzyQueryBuilder
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "李四");
2.9:RangeQueryBuilder
RangeQueryBuilder ageRangeFilter = QueryBuilders.rangeQuery("age")
    // greater than 12
    .gt(12)
    // less than 17
    .lt(17);


// 默认是true包含头尾,设置false去掉头尾
RangeQueryBuilder ageRangeFilter2 = QueryBuilders.rangeQuery("age")
    .from(12)
    .to(17)
    // 不包含最后一个元素
    .includeLower(false)
    // 包含第一个元素
    .includeUpper(true);
2.10:WildcardQueryBuilder
// wildcard 通配符查询, 支持*,匹配任何字符序列, 包括空,避免*
String queryString = "Lc*dd";
WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("name", queryString);
2.11:BoolQueryBuilder
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
    .must(QueryBuilders.termQuery("name.keyword", "王五"))
    .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
    // 可以进行bool嵌套
    .must(QueryBuilders.boolQuery()
          .must(QueryBuilders.matchQuery("name", "张三"))
         )
    .boost(2.0f);
searchSourceBuilder.query(boolQueryBuilder);
2.12:其他的QueryBuilder
// 16: 其他查询
// moreLikeThisQuery: 实现基于内容推荐, 支持实现一句话相似文章查询
// percent_terms_to_match:匹配项(term)的百分比,默认是0.3
// min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
// max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25
// stop_words:设置停止词,匹配时会忽略停止词
// min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制
// max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制
// min_word_len:最小的词语长度,默认是0
// max_word_len:最多的词语长度,默认无限制
// boost_terms:设置词语权重,默认是1
// boost:设置查询权重,默认是1
// analyzer:设置使用的分词器,默认是使用该字段指定的分词器
QueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery(new String[]{"王"})
    // 一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
    .minTermFreq(1)
    // 一条查询语句中允许最多查询词语的个数,默认是25
    .maxQueryTerms(3);


// 查询条件
searchSourceBuilder.query(matchQueryBuilder)
    // 后置过滤器,id, exists, term, range
    .postFilter(QueryBuilders.existsQuery("tag"));

3:聚合建造器说明

3.1:构造聚合条件
TermsAggregationBuilder aggregation = 
    AggregationBuilders.terms(等于的值).field(字段名称) // 进行分桶操作
        .subAggregation(AggregationBuilders.avg(桶的名字,随便起的).field(根据xx字段分桶)) // 对每一个分桶,进行子聚合
3.2:将聚合条件交给builder,builder交给源,封装request
// 声明源
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

// 将查询建造器放入源,将高亮信息放入源,将聚合信息放入源
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.highlighter(highlightBuilder);
searchSourceBuilder.aggregation(aggregation);

// 将源放入请求request
SearchRequest request = new SearchRequest(index).source(searchSourceBuilder);

// 进行查询
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
3.3:结果解析和处理
// 结果解析
List<Object> ans = handleResult(response);
// 结果处理
for (Object o : ans) {
    System.out.println(o.toString());
}
// todo: 结果解析封装
/**
 * 聚合查询
 */
public void aggTest() {
    // 声明查询请求,同时声明作用的索引(数据库)
    SearchRequest request = new SearchRequest();
    request.indices("person");

    // 声明searchSourceBuilder源对象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

    // ======================== 源对象其他常见设置 ===========================
    // 1. 排序和浅显分页
    // from  & size 默认都是-1,也就是有多少显示多少
    // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
    searchSourceBuilder.from(0);
    searchSourceBuilder.size(9999);

    // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
    searchSourceBuilder
            .sort("age", SortOrder.DESC)
            .sort("name", SortOrder.ASC);


    // 构造查询建造器,根据不同的业务需求,进行不同的查询
    // =============== 以boolean为例 ==================
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
            .must(QueryBuilders.termQuery("name.keyword", "王五"))
            .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
            // 可以进行bool嵌套
            .must(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("name", "张三"))
            )
            .boost(2.0f);
    // 请求构造器加入源中
    searchSourceBuilder.query(boolQueryBuilder);


    // ============ 如果有高亮设置 ===============
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<font style='color: red'>");
    highlightBuilder.postTags("</font>");
    highlightBuilder.field("name").field("age");
    //同一字段中存在多个高亮值 设置都高亮
    highlightBuilder.requireFieldMatch(true);
    // 高亮属性中加入源中
    searchSourceBuilder.highlighter(highlightBuilder);

    TermsAggregationBuilder aggregation =
            // 进行分桶操作
            AggregationBuilders.terms("Jone").field("name")
                    // 对每一个分桶,进行子聚合
                    .subAggregation(AggregationBuilders.avg("age_avg").field("age"));
    // 将聚合结果放入源中
    searchSourceBuilder.aggregation(aggregation);

    // 进行查询
    // 1. 设置查询源(source -> request)
    request.source(searchSourceBuilder);
    // 2. 通过search方法进行查询
    List<Map<String, Object>> list = new ArrayList<>();
    try {
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 结果处理
        System.out.println("response.getHits().getTotalHits() = " + response.getHits().getTotalHits());


        // 然后从这个searchResponse解析各种的值 -> 根据返回结构
        int failedShards = response.getFailedShards();
        System.out.println("失败的分片数:" + failedShards);
        int successfulShards = response.getSuccessfulShards();
        System.out.println("成功的分片数:" + successfulShards);
        RestStatus status = response.status();
        System.out.println("状态:" + status);

        // 解析高亮数据
        SearchHits hits = response.getHits();
        System.out.println(hits.getMaxScore());
        for (SearchHit hit : hits) {
            System.out.println("fields: " + hit.getFields());
            System.out.println("index is:" + hit.getIndex());
            System.out.println("document field is: " + hit.getDocumentFields());
            System.out.println("metadata field is: " + hit.getMetadataFields());
            System.out.println("score is: " + hit.getScore());
            System.out.println("-----------------");
            // 原始数据,不包含高亮的数据
            Map<String, Object> sourceMap = hit.getSourceAsMap();
            // 高亮数据,拿到高亮字段name
            Map<String, org.elasticsearch.search.fetch.subphase.highlight.HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField highlightTitle = highlightFields.get("name");
            // 将原始数据的name替换
            if (highlightTitle != null) {
                Text[] fragments = highlightTitle.getFragments();
                if (fragments != null && fragments.length > 0) {
                    sourceMap.replace("name", fragments[0].toString());
                }
            }
            // 循环将数据添加入列表
            list.add(sourceMap);
        }
        list.forEach(System.out::println);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

三:repo & template

1:ElasticsearchRepository

1.1:repo介绍

ElasticsearchRepository接口封装了Document的CRUD操作,我们直接定义接口继承它即可。
在这里插入图片描述

ElasticsearchRepository接口的源码

package org.springframework.data.elasticsearch.repository;

import java.io.Serializable;

import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
    <S extends T> S index(S entity);
    Iterable<T> search(QueryBuilder query);
    Page<T> search(QueryBuilder query, Pageable pageable);
    Page<T> search(SearchQuery searchQuery);
    Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);
    void refresh();
    Class<T> getEntityClass();
}

CrudRepository源码

package org.springframework.data.repository;

import java.util.Optional;

/**
 * Interface for generic CRUD operations on a repository for a specific type.
 *
 * @author Oliver Gierke
 * @author Eberhard Wolff
 */
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    // 保存相关
    <S extends T> S save(S entity);
    <S extends T> Iterable<S> saveAll(Iterable<S> entities);
    
    // 查找相关
    Optional<T> findById(ID id);
    boolean existsById(ID id);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> ids);
    
    // 计数
    long count();
    
    // delete相关
    void deleteById(ID id);
    void delete(T entity);
    void deleteAll(Iterable<? extends T> entities);
    void deleteAll();
}

PagingAndSortingRepository源码

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends Repository<T, ID> {
    Iterable<T> findAll(Sort sort);
    Page<T> findAll(Pageable pageable);
}

可见ElasticsearchRepository为我们封装好了很多常用的方法,我们可以在使用的时候直接调用这些方法进行操作

1.2:自定义方法命名规范

和所有的Spring Data一样,都支持指定格式的函数声明,对于这些函数,不用写具体的实现,而是可以直接调用

SpringData会通过动态代理的方式,帮我们生成基础的CRUD方法

自定义方法命名规范

  • findBy[fieldName]:根据指定的单个条件进行等值查询;
  • findBy[fieldName]And[fieldName]And[...]:根据指定的多条件进行and查询;
  • findBy[fieldName]Or[fieldName]Or[...]:根据指定的多条件进行or查询;
  • findBy[fieldName]Equals:根据指定的单个条件进行等值查询;
  • findBy[fieldName]In:对指定的单个字段进行in查询,入参为一个列表;
  • findBy[fieldName]Like:对指定的单个字段进行like模糊查询;
  • findBy[fieldName]NotNull:查询指定字段不为空的数据;
  • findBy[fieldName]GreaterThan:对指定的单个字段进行]范围查询;
  • findBy[fieldName]GreaterThanEqual:对指定的单个字段进行]=范围查询;
  • findBy[fieldName]LessThan:对指定的单个字段进行[范围查询;
  • findBy[fieldName]LessThanEqual:对指定的单个字段进行[=范围查询;
  • Page[...] findBy[...]:根据指定的条件进行分页查询;
  • countBy[fieldName]:根据指定的条件字段进行计数统计;
  • findTop[n]By[fieldName]:根据指定字段做等值查询,并返回前n条数据;
  • findBy[fieldName]Between:根据指定字段进行between范围查询;
  • findDistinctBy[fieldName]:根据指定的单个条件进行去重查询;
  • findFirstBy[fieldName]:根据指定的单个条件进行等值查询(只返回满足条件的第一个数据);
  • findBy[fieldName1]OrderBy[fieldName2]:根据第一个字段做等值查询,并根据第二个字段做排序;

各种方法名开头含义

  • get、find、read、query、stream开头,代表是查询数据的方法;
  • count开头,代表是计数统计的方法;
  • delete、remove开头,代表是删除数据的方法;
  • exists开头,代表是判断是否存在的方法;
  • search开头,代表是全文搜索的方法;
  • update开头,代表是修改数据的方法;

方法名开头后面跟的关键字含义,以find开头的方法为例:

  • By:表示当前方法生成的查询语句,会根据By后面的逻辑来组成;
  • FirstBy:表示当前方法生成的语句,只会返回符合条件的第一条数据;
  • DistinctBy:表示当前方法生成的语句,会对符合条件的数据去重;
  • TopBy:表示当前方法生成的语句,只会返回符合条件的前N条数据;
  • [实体类名称]By:表示当前方法生成的语句,只会返回一条数据;
  • [实体类名称]sBy:表示当前方法生成的语句,会返回多条数据;
  • AllBy:表示当前方法生成的语句,会返回多条或所有数据;
  • DistinctFirstBy:表示当前方法生成的语句,只会返回去重后的第一条数据;
  • DistinctTopBy:表示当前方法生成的语句,只会返回去重后的前N条数据;

方法名开头跟的关键字之后跟的是字段名[fieldName], 字段名称后面可以接的关键字如下

在这些关键字之后,都是跟具体的字段名(实体类的属性名),字段名称后面可以接的关键字如下(同样以find为例):

  • Or:表示当前查询方法有多个条件,多个条件之间为“或者”关系;
  • And:表示当前查询方法有多个条件,多个条件之间为“并且”关系;
  • OrderBy:表示当前查询会涉及到排序,后面需要跟一个排序字段;
  • Between:表示当前方法为between范围查询;
  • GreaterThan:表示当前方法为>查询;
  • GreaterThanEqual:表示当前方法为>=查询;
  • LessThan:表示当前方法为 < 查询;
  • LessThanEqual:表示当前方法为<=查询;
  • After:和GreaterThan差不多,相当于查询指定数值之后的数据;
  • Before:和LessThan差不多,查询指定条件之前的数据;
  • Containing:查询某字段中包含指定字符的数据;
  • Empty:表示当前方法会查询指定字段为空的数据,与之含义类似的还有Null、Exists;
  • Equals:表示当前方法会根据指定字段做等值查询;
  • Is:和Equals差不多;
  • In:表示当前方法为in多值匹配查询;
  • Like:表示当前方法为like模糊查询;
  • Not:可以和上述大多数关键字组合,带有Not的则含义相反,如NotEmpty表示不为空;

在这里插入图片描述
在这里插入图片描述

2:ElasticsearchRestTemplate

2.1:template介绍

template提供了众多模板方法,只要我们编写好对应的条件,然后调用模板方法即可

在这里插入图片描述
在这里插入图片描述

2.2:template查询操作

下面是操作流程【查询为例】

2.2.1:入参特殊情况处理【包括异常入参】
2.2.2:构建查询条件Criteria
// 1:构建查询条件
Criteria criteria = new Criteria()
    // 条件一:xxxx
    .and(new Criteria("字段名称").contains(条件中的内容))
    // 条件二:xxx
    .and(new Criteria("字段名称").is(条件中的内容));
// 2:封装进入到CriteriaQuery
CriteriaQuery filter = new CriteriaQuery(criteria);
// 3:设置分页信息【可能没有,看业务】
CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
2.2.3:构建高亮条件
// 1:声明高亮构造器对象
HighlightBuilder highlightBuilder = new HighlightBuilder(); 
// todo: 设置高亮领域
// todo: 前置后置标签

// 2:构建高亮query
HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
// 3:将高亮query封装进criteriaQuery中
criteriaQuery.setHighlightQuery(highlightQuery);
2.2.4:查询(也可能是其他的方法,不一定是search)
elasticsearchRestTemplate.search(查询条件,返回信息的实体.class);
2.2.5:结果封装
// 对结果进行封装

2.3:创建索引和删除索引

对于索引的创建和删除,建议还是直接使用Kibana进行相关的操作,如果要使用template,可以输入对应的索引名称进行创建

template会到实体中找到@Document(indexName = "xxx")对应的信息,根据这个注解的配置拿到settings对应的信息配置

然后根据实体类标注的字段类型和分析器类型创建对应的mappings对应的信息配置

package com.example.es_demo.pojo;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.time.LocalDateTime;

/**
 * <p>
 * 功能描述:实体类
 * </p>
 *
 * @author cui haida
 * @date 2024/01/28/8:20
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Document(indexName = "mytest") // 指定对应的索引名称
public class MyTest {
    @Id
    @JSONField(serialize = false)
    private String id;

    @Field(type = FieldType.Keyword)
    private String name;

    @Field(type = FieldType.Keyword)
    private String age;

    // text类型,并使用IK最粗粒度的分词器,检索时的分词器采用的是最细粒度的IK分词器
    @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")
    private String address;

    // 注意日期格式的特殊处理
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
}
public String create(String indexName) {
    IndexOperations indexOperations = 
        elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
    if (indexOperations.exists()) {
        return "索引已存在";
    }
    indexOperations.create();
    return "索引创建成功";
}
public String delete(String indexName) {
    IndexOperations indexOperations = 
        elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
    indexOperations.delete();
    return "索引删除成功";
}

3:使用实例

package com.cui.es_demo.client;

import com.cui.es_demo.client.repo.PersonRepository;
import com.cui.es_demo.entity.PageResponse;
import com.cui.es_demo.entity.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
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.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author cui haida
 * 2025/1/30
 */
@Service
@Slf4j
public class TemplateDemo {
    // es repo -> 这里提供了Spring Data的基本方法,可以直接使用
    @Autowired
    PersonRepository personRepository;
    // template
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;


    // =========== repo测试 =============
    // 就是直接调用对应的方法,只要语法满足上面说的Spring Data语法就可以
    public void saveAll(List<Person> orders) {
        personRepository.saveAll(orders);
    }

    public void deleteById(Integer id) {
        personRepository.deleteById(id);
    }
    public void updateById(Person person) {
        personRepository.save(person);
    }
    public Person findById(Integer id) {
        return (Person) personRepository.findById(id).orElse(null);
    }

    public PageResponse<Person> findAll(Integer pageIndex, Integer pageSize) {
        Page<Person> page = personRepository.findAll(PageRequest.of(pageIndex, pageSize));
        PageResponse<Person> pageResponse = new PageResponse<Person>();
        pageResponse.setTotal(page.getTotalElements());
        pageResponse.setResult(page.getContent());
        return pageResponse;
    }

    // =============== template测试 ==============
    // 1:构造查询条件
    // 2:调用template.xxx(条件)
    // 3:结果处理(封装)
    public PageResponse<Person> findList(Person person, Integer pageIndex, Integer pageSize) {
        // 1:构建查询条件
        Criteria criteria = new Criteria()
                // 条件一:文档中的orderDesc字段要包含查询条件的orderDesc字段
                .and(new Criteria("orderDesc").contains(person.getAddress()))
                // 条件二:文档中的orderNo字段要等于查询条件的orderNo字段
                .and(new Criteria("orderNo").is(person.getId()));
        // 封装进入到CriteriaQuery
        CriteriaQuery filter = new CriteriaQuery(criteria);
        // 设置分页信息
        CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));

        // 2:查询 -> elasticsearchRestTemplate.search(查询条件,返回信息的实体.class)
        SearchHits<Person> searchHits = elasticsearchRestTemplate.search(criteriaQuery, Person.class);

        // 3:结果封装
        List<Person> result = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
        PageResponse<Person> pageResponse = new PageResponse<>();
        pageResponse.setTotal(searchHits.getTotalHits());
        pageResponse.setResult(result);
        return pageResponse;
    }

    public PageResponse<Person> findHighlight(Person person, Integer pageIndex, Integer pageSize) {
        // 0:特殊情况的处理
        if (person == null) {
            PageResponse<Person> pageResponse = new PageResponse<Person>();
            pageResponse.setTotal(0L);
            pageResponse.setResult(new ArrayList<>());
            return pageResponse;
        }

        // 1:构建查询条件
        Criteria criteria = new Criteria()
                // 条件一:文档中的orderDesc字段要包含查询条件的orderDesc字段
                .and(new Criteria("id").contains(person.getId()))
                // 条件二:文档中的orderNo字段要等于查询条件的orderNo字段
                .and(new Criteria("address").is(person.getAddress()));
        // 封装进入到CriteriaQuery
        CriteriaQuery filter = new CriteriaQuery(criteria);
        // 设置分页信息
        CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));

        // 3:查询 -> elasticsearchRestTemplate.search(查询条件,返回信息的实体.class)
        SearchHits<Person> searchHits = elasticsearchRestTemplate.search(criteriaQuery, Person.class);
        // 4:结果封装
        // 对每一个都设置对应的高亮字段信息
        List<Person> result = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
        // 封装页码信息
        PageResponse<Person> pageResponse = new PageResponse<>();
        pageResponse.setTotal(searchHits.getTotalHits());
        pageResponse.setResult(result);
        return pageResponse;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2291049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java篇之继承

目录 一. 继承 1. 为什么需要继承 2. 继承的概念 3. 继承的语法 4. 访问父类成员 4.1 子类中访问父类的成员变量 4.2 子类中访问父类的成员方法 5. super关键字 6. super和this关键字 7. 子类构造方法 8. 代码块的执行顺序 9. protected访问修饰限定符 10. 继承方式…

ArkTS编程规范

文章目录 目标和适用范围规则来源章节概览代码风格编程实践 术语和定义总体原则命名类名、枚举名、命名空间名采用UpperCamelCase风格变量名、方法名、参数名采用lowerCamelCase风格常量名、枚举值名采用全部大写&#xff0c;单词间使用下划线隔开避免使用否定的布尔变量名&…

深度学习之“向量范数和距离度量”

在深度学习中&#xff0c;范数和向量距离是两个不同的概念。向量范数是一种函数&#xff0c;用于将一个实数或复数向量映射为一个值。虽然范数通常用于度量向量之间的距离&#xff0c;但是同样也有其它的一些表示距离的方式。 范数距离 范数是具有“长度”概念的函数。在向量…

基于Python的简单企业维修管理系统的设计与实现

以下是一个基于Python的简单企业维修管理系统的设计与实现&#xff0c;这里我们会使用Flask作为Web框架&#xff0c;SQLite作为数据库来存储相关信息。 1. 需求分析 企业维修管理系统主要功能包括&#xff1a; 维修工单的创建、查询、更新和删除。设备信息的管理。维修人员…

< OS 有关 > Android 手机 SSH 客户端 app: connectBot

connectBot 开源且功能齐全的SSH客户端,界面简洁,支持证书密钥。 下载量超 500万 方便在 Android 手机上&#xff0c;连接 SSH 服务器&#xff0c;去运行命令。 Fail2ban 12小时内抓获的 IP ~ ~ ~ ~ rootjpn:~# sudo fail2ban-client status sshd Status for the jail: sshd …

【算法设计与分析】实验7:复杂装载及0/1背包问题的回溯法设计与求解

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 针对复杂装载问题、及0/1背包问题开展分析、建模、评价&#xff0c;算法设计与优化&#xff0c;并进行编码实践。 理解复杂装载…

仿真设计|基于51单片机的温湿度、一氧化碳、甲醛检测报警系统

目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现&#xff08;protues8.7&#xff09; 程序&#xff08;Keil5&#xff09; 全部内容 资料获取 具体实现功能 &#xff08;1&#xff09;温湿度传感器、CO传感器、甲醛传感器实时检测温湿度值、CO值和甲醛值进…

使用vhd虚拟磁盘安装两个win10系统

使用vhd虚拟磁盘安装两个win10系统 前言vhd虚拟磁盘技术简介准备工具开始动手实践1.winX选择磁盘管理2.选择“操作”--“创建VHD”3.自定义一个位置&#xff0c;输入虚拟磁盘大小4.右键初始化磁盘5.选择GPT分区表格式6.右键新建简单卷7.给卷起个名字&#xff0c;用于区分8.打开…

深入理解Spring事务管理

一、事务基础概念 1.1 什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;是数据库操作的最小工作单元&#xff0c;具有ACID四大特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务中的操作要么全部成功&#xff0c;要么全部失败 一致…

自制虚拟机(C/C++)(二、分析引导扇区,虚拟机读二进制文件img软盘)

先修复上一次的bug&#xff0c;添加新指令&#xff0c;并增加图形界面 #include <graphics.h> #include <conio.h> #include <windows.h> #include <commdlg.h> #include <iostream> #include <fstream> #include <sstream> #inclu…

ASP.NET Core 启动并提供静态文件

ASP.NET Core 启动并提供静态文件 即是单个可执行文件&#xff0c;它既运行 API 项目&#xff0c;也托管 前端项目&#xff08;通常是前端的发布文件&#xff09;。 这种方式一般是通过将 前端项目 的发布文件&#xff08;例如 HTML、CSS、JavaScript&#xff09;放入 Web AP…

4 [危机13小时追踪一场GitHub投毒事件]

事件概要 自北京时间 2024.12.4 晚间6点起&#xff0c; GitHub 上不断出现“幽灵仓库”&#xff0c;仓库中没有任何代码&#xff0c;只有诱导性的病毒文件。当天&#xff0c;他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒&#xff0c;等待不…

变量和常量

一.变量 1.标准声明 var 变量名 变量类型 变量声明行末不需要分号 2..批量声明 package main import "fmt" func main(){var(a string b int c boold float32)}3.变量的初始化 var a int 10 var b float321.1 4.类型推导 var name"tom" var age18 fmt.Pr…

大模型概述(方便不懂技术的人入门)

1 大模型的价值 LLM模型对人类的作用&#xff0c;就是一个百科全书级的助手。有多么地百科全书&#xff0c;则用参数的量来描述&#xff0c; 一般地&#xff0c;大模型的参数越多&#xff0c;则该模型越好。例如&#xff0c;GPT-3有1750亿个参数&#xff0c;GPT-4可能有超过1万…

流浪 Linux: 外置 USB SSD 安装 ArchLinux

注: ArchLinux 系统为滚动更新, 变化很快, 所以本文中的安装方法可能很快就过时了, 仅供参考. 实际安装时建议去阅读官方文档. 最近, 突然 (也没有那么突然) 有了一大堆 PC: 4 个笔记本, 2 个台式主机 (M-ATX 主板), 1 个小主机 (迷你主机). 嗯, 多到用不过来. 但是, 窝又不能…

Hot100之子串

560和为K的子数组 题目 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列 思路解析 ps&#xff1a;我们的presum【0】就是0&#xff0c;如果没有这个0的话我们的第一个元素就无法减去上…

网络工程师 (11)软件生命周期与开发模型

一、软件生命周期 前言 软件生命周期&#xff0c;也称为软件开发周期或软件开发生命周期&#xff0c;是指从软件项目的启动到软件不再被使用为止的整个期间。这个过程可以细分为多个阶段&#xff0c;每个阶段都有其特定的目标、任务和产出物。 1. 问题定义与需求分析 问题定义…

(三)QT——信号与槽机制——计数器程序

目录 前言 信号&#xff08;Signal&#xff09;与槽&#xff08;Slot&#xff09;的定义 一、系统自带的信号和槽 二、自定义信号和槽 三、信号和槽的扩展 四、Lambda 表达式 总结 前言 信号与槽机制是 Qt 中的一种重要的通信机制&#xff0c;用于不同对象之间的事件响…

hot100_21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[…

安全防护前置

就业概述 网络安全工程师/安全运维工程师/安全工程师 安全架构师/安全专员/研究院&#xff08;数学要好&#xff09; 厂商工程师&#xff08;售前/售后&#xff09; 系统集成工程师&#xff08;所有计算机知识都要会一点&#xff09; 学习目标 前言 网络安全事件 蠕虫病毒--&…