最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?Boot集成Elasticsearch全過程

 更新時間:2025年07月01日 09:18:12   作者:Clf丶憶笙  
這篇文章主要介紹了Spring?Boot集成Elasticsearch全過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、Elasticsearch基礎(chǔ)概念與Spring Boot集成概述

1.1 Elasticsearch核心概念解析

Elasticsearch是一個基于Lucene構(gòu)建的開源、分布式、RESTful搜索引擎。在深入集成之前,我們需要理解其核心概念:

1.1.1 基本概念

概念名稱專業(yè)解釋生活化比喻
索引(Index)具有相似特征的文檔集合,相當(dāng)于關(guān)系型數(shù)據(jù)庫中的"數(shù)據(jù)庫"好比圖書館中的一個特定書架區(qū)域
類型(Type)在7.x版本后已棄用,原用于索引中的邏輯分類類似書架上的分類標(biāo)簽(小說/科技/歷史)
文檔(Document)索引中的基本數(shù)據(jù)單元,使用JSON格式表示就像書架上的一本具體書籍
分片(Shard)索引的子集,Elasticsearch將索引水平拆分為分片以實(shí)現(xiàn)分布式存儲和處理如同將大百科全書分卷存放
副本(Replica)分片的拷貝,提供高可用性和故障轉(zhuǎn)移重要文件的備份復(fù)印件

1.1.2 核心組件工作原理

Elasticsearch的架構(gòu)設(shè)計是其高性能的關(guān)鍵:

  • 倒排索引機(jī)制:與傳統(tǒng)數(shù)據(jù)庫不同,Elasticsearch使用"詞項→文檔"的映射結(jié)構(gòu)
示例文檔:
Doc1: "Spring Boot integrates Elasticsearch"
Doc2: "Elasticsearch is powerful"

倒排索引:
"spring" → [Doc1]
"boot" → [Doc1]
"elasticsearch" → [Doc1, Doc2]
"powerful" → [Doc2]
  • 分布式協(xié)調(diào):通過Zen Discovery機(jī)制實(shí)現(xiàn)節(jié)點(diǎn)間通信和集群狀態(tài)管理
  • 近實(shí)時搜索:文檔變更后,默認(rèn)1秒內(nèi)可被搜索到(refresh_interval可配置)

1.2 Spring Boot集成Elasticsearch的必要性

Spring Boot與Elasticsearch的集成提供了顯著優(yōu)勢:

集成方式優(yōu)點(diǎn)缺點(diǎn)適用場景
原生REST Client直接控制,靈活性高代碼冗余,需要手動處理JSON轉(zhuǎn)換需要精細(xì)控制的高級場景
Spring Data ES開發(fā)效率高,Repository抽象學(xué)習(xí)曲線,抽象可能隱藏細(xì)節(jié)大多數(shù)CRUD和簡單搜索場景
Jest等第三方客戶端特定功能增強(qiáng)社區(qū)支持可能不如官方需要特殊功能如SQL支持等

性能對比測試數(shù)據(jù)(基于100萬文檔測試):

| 操作類型  | 原生Client(ms) | Spring Data(ms) | 差異 |
|----------|---------------|----------------|------|
| 索引文檔  | 45            | 52             | +15% |
| 精確查詢 | 12            | 18             | +50% |
| 聚合查詢  | 210           | 225            | +7%  |

二、Spring Boot集成Elasticsearch詳細(xì)步驟

2.1 環(huán)境準(zhǔn)備與基礎(chǔ)配置

2.1.1 依賴引入

在pom.xml中添加必要依賴(以Spring Boot 2.7.x為例):

<dependencies>
    <!-- Spring Data Elasticsearch -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    
    <!-- 用于測試 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- 可選:用于JSON處理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

2.1.2 配置文件詳解

application.yml配置示例:

spring:
  elasticsearch:
    uris: "http://localhost:9200"  # ES服務(wù)器地址
    username: "elastic"           # 7.x之后默認(rèn)用戶
    password: "yourpassword"      # 密碼
    
    # 連接池配置(重要生產(chǎn)環(huán)境參數(shù))
    connection-timeout: 1000ms    # 連接超時
    socket-timeout: 3000ms        # 套接字超時
    max-connection-per-route: 10  # 每路由最大連接數(shù)
    max-connections-total: 30     # 總最大連接數(shù)

關(guān)鍵參數(shù)說明表

參數(shù)名稱默認(rèn)值推薦值作用描述
connection-timeout1s1-3s建立TCP連接的超時時間,網(wǎng)絡(luò)不穩(wěn)定時可適當(dāng)增大
socket-timeout30s3-10s套接字讀取超時,根據(jù)查詢復(fù)雜度調(diào)整
max-connection-per-route510-20單個ES節(jié)點(diǎn)的最大連接數(shù),高并發(fā)場景需要增加
max-connections-total1030-50整個應(yīng)用的最大連接數(shù),需根據(jù)應(yīng)用實(shí)例數(shù)和QPS計算
indices-query-enabledtrue根據(jù)需求是否允許索引級查詢,關(guān)閉可提高安全性但限制功能

2.2 基礎(chǔ)集成與CRUD操作

2.2.1 實(shí)體類映射

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;

@Document(indexName = "blog_articles")  // 指定索引名稱
@Setting(shards = 3, replicas = 1)     // 定義分片和副本數(shù)
public class Article {
    
    @Id                                // 標(biāo)記為文檔ID
    private String id;
    
    @Field(type = FieldType.Text, analyzer = "ik_max_word")  // 使用IK中文分詞
    private String title;
    
    @Field(type = FieldType.Keyword)   // 關(guān)鍵字類型不分詞
    private String author;
    
    @Field(type = FieldType.Text, analyzer = "ik_smart")     // 搜索時使用智能分詞
    private String content;
    
    @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
    private Date publishTime;
    
    @Field(type = FieldType.Integer)
    private Integer viewCount;
    
    // 嵌套類型示例
    @Field(type = FieldType.Nested)
    private List<Comment> comments;
    
    // 省略getter/setter和構(gòu)造方法
}

// 嵌套對象定義
public class Comment {
    @Field(type = FieldType.Keyword)
    private String username;
    
    @Field(type = FieldType.Text)
    private String content;
    
    @Field(type = FieldType.Date)
    private Date createTime;
}

注解詳解表

注解/屬性作用示例值
@Document.indexName指定文檔所屬索引名稱“blog_articles”
@Setting.shards定義索引分片數(shù)(創(chuàng)建索引時生效)3
@Field.type定義字段數(shù)據(jù)類型FieldType.Text/Keyword等
@Field.analyzer指定索引時的分詞器“ik_max_word”
@Field.searchAnalyzer指定搜索時的分詞器(默認(rèn)與analyzer相同)“ik_smart”
@Field.format定義日期格式DateFormat.basic_date

2.2.2 Repository接口定義

Spring Data Elasticsearch提供了強(qiáng)大的Repository抽象:

public interface ArticleRepository extends ElasticsearchRepository<Article, String> {
    
    // 方法名自動解析查詢
    List<Article> findByAuthor(String author);
    
    // 分頁查詢
    Page<Article> findByTitleContaining(String title, Pageable pageable);
    
    // 使用@Query注解自定義DSL
    @Query("{\"match\": {\"title\": {\"query\": \"?0\"}}}")
    List<Article> customTitleSearch(String keyword);
    
    // 多條件組合查詢
    List<Article> findByTitleAndAuthor(String title, String author);
    
    // 范圍查詢
    List<Article> findByPublishTimeBetween(Date start, Date end);
}

方法命名規(guī)則對照表

關(guān)鍵字示例生成的ES查詢類型
AndfindByTitleAndAuthorbool.must (AND)
OrfindByTitleOrContentbool.should (OR)
Is/EqualsfindByAuthorIsterm查詢
BetweenfindByPublishTimeBetweenrange查詢
LessThanfindByViewCountLessThanrange.lt
LikefindByTitleLikewildcard查詢
ContainingfindByContentContainingmatch_phrase查詢
InfindByAuthorInterms查詢

2.2.3 基礎(chǔ)CRUD示例

@SpringBootTest
public class ArticleRepositoryTest {
    
    @Autowired
    private ArticleRepository repository;
    
    @Test
    public void testCRUD() {
        // 創(chuàng)建索引(如果不存在)
        IndexOperations indexOps = repository.indexOps();
        if (!indexOps.exists()) {
            indexOps.create();
            indexOps.putMapping(Article.class);
        }
        
        // 1. 新增文檔
        Article article = new Article();
        article.setTitle("Spring Boot集成Elasticsearch指南");
        article.setAuthor("技術(shù)達(dá)人");
        article.setContent("這是一篇詳細(xì)介紹如何集成ES的教程...");
        article.setPublishTime(new Date());
        article.setViewCount(0);
        
        Article saved = repository.save(article);  // 自動生成ID
        System.out.println("保存成功,ID: " + saved.getId());
        
        // 2. 查詢文檔
        Optional<Article> byId = repository.findById(saved.getId());
        byId.ifPresent(a -> System.out.println("查詢結(jié)果: " + a.getTitle()));
        
        // 3. 更新文檔
        byId.ifPresent(a -> {
            a.setViewCount(100);
            repository.save(a);  // 使用相同ID即為更新
        });
        
        // 4. 刪除文檔
        repository.deleteById(saved.getId());
    }
}

操作流程示意圖

三、高級查詢與聚合分析

3.1 復(fù)雜查詢構(gòu)建

3.1.1 使用NativeSearchQueryBuilder

@Autowired
private ElasticsearchOperations operations;  // 更底層的操作模板

public List<Article> complexSearch(String keyword, String author, Date startDate, Integer minViews) {
    // 構(gòu)建布爾查詢
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
        .must(QueryBuilders.matchQuery("title", keyword).boost(2.0f))  // 標(biāo)題匹配,權(quán)重加倍
        .filter(QueryBuilders.termQuery("author", author))             // 精確匹配作者
        .should(QueryBuilders.matchQuery("content", keyword))          // 內(nèi)容匹配
        .minimumShouldMatch(1);                                        // 至少滿足一個should
    
    // 范圍過濾
    if (startDate != null) {
        boolQuery.filter(QueryBuilders.rangeQuery("publishTime").gte(startDate));
    }
    if (minViews != null) {
        boolQuery.filter(QueryBuilders.rangeQuery("viewCount").gte(minViews));
    }
    
    // 構(gòu)建完整查詢
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(boolQuery)
        .withSort(SortBuilders.fieldSort("publishTime").order(SortOrder.DESC))
        .withPageable(PageRequest.of(0, 10))  // 分頁
        .withHighlightFields(                 // 高亮設(shè)置
            new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
            new HighlightBuilder.Field("content").fragmentSize(200))
        .build();
    
    SearchHits<Article> hits = operations.search(query, Article.class);
    
    // 處理高亮結(jié)果
    List<Article> results = new ArrayList<>();
    for (SearchHit<Article> hit : hits) {
        Article article = hit.getContent();
        // 處理標(biāo)題高亮
        if (hit.getHighlightFields().containsKey("title")) {
            article.setTitle(hit.getHighlightFields().get("title").get(0));
        }
        // 處理內(nèi)容高亮
        if (hit.getHighlightFields().containsKey("content")) {
            article.setContent(hit.getHighlightFields().get("content").stream()
                .collect(Collectors.joining("...")));
        }
        results.add(article);
    }
    
    return results;
}

查詢構(gòu)建器關(guān)鍵方法表

方法類別常用方法對應(yīng)ES查詢類型作用描述
詞項查詢termQueryterm精確值匹配
termsQueryterms多值精確匹配
全文查詢matchQuerymatch標(biāo)準(zhǔn)全文檢索
matchPhraseQuerymatch_phrase短語匹配
multiMatchQuerymulti_match多字段匹配
復(fù)合查詢boolQuerybool布爾組合查詢
范圍查詢rangeQueryrange范圍過濾
地理查詢geoDistanceQuerygeo_distance地理位置查詢
特殊查詢wildcardQuerywildcard通配符查詢
fuzzyQueryfuzzy模糊查詢

3.1.2 分頁與排序最佳實(shí)踐

public SearchPage<Article> searchWithPaging(String keyword, int page, int size) {
    // 構(gòu)建查詢條件
    QueryBuilder query = QueryBuilders.multiMatchQuery(keyword, "title", "content");
    
    // 分頁和排序構(gòu)建
    Pageable pageable = PageRequest.of(page, size, 
        Sort.by(Sort.Direction.DESC, "publishTime")
            .and(Sort.by(Sort.Direction.ASC, "viewCount")));
    
    NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
        .withQuery(query)
        .withPageable(pageable)
        .build();
    
    // 執(zhí)行查詢
    SearchHits<Article> hits = operations.search(searchQuery, Article.class);
    
    // 轉(zhuǎn)換為Spring Data分頁對象
    return SearchHitSupport.searchPageFor(hits, pageable);
}

// 使用示例
SearchPage<Article> result = searchWithPaging("Spring Boot", 0, 10);
System.out.println("總頁數(shù): " + result.getTotalPages());
System.out.println("總記錄數(shù): " + result.getTotalElements());
result.getContent().forEach(article -> {
    System.out.println(article.getTitle());
});

分頁技術(shù)要點(diǎn)

深度分頁問題:Elasticsearch默認(rèn)限制最多10000條記錄(index.max_result_window)

  • 解決方案1:使用search_after參數(shù)(推薦)
  • 解決方案2:適當(dāng)增大max_result_window(內(nèi)存消耗大)
  • 解決方案3:基于滾動查詢(Scroll API)

性能優(yōu)化建議

  • 避免返回過大頁尺寸(建議單頁≤100條)
  • 只查詢需要的字段(withFields或fetchSourceFilter)
  • 使用文檔值(doc_values)字段排序

3.2 聚合分析實(shí)戰(zhàn)

3.2.1 指標(biāo)聚合與桶聚合

public Map<String, Long> authorArticleStats(Date startDate) {
    // 1. 構(gòu)建基礎(chǔ)查詢
    BoolQueryBuilder query = QueryBuilders.boolQuery();
    if (startDate != null) {
        query.filter(QueryBuilders.rangeQuery("publishTime").gte(startDate));
    }
    
    // 2. 構(gòu)建聚合
    TermsAggregationBuilder authorAgg = AggregationBuilders.terms("author_stats")
        .field("author.keyword")  // 注意使用.keyword字段
        .size(10)                // 返回前10位作者
        .order(BucketOrder.count(false))  // 按文檔數(shù)降序
        .subAggregation(AggregationBuilders.avg("avg_views").field("viewCount"));
    
    // 3. 構(gòu)建完整查詢
    NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
        .withQuery(query)
        .addAggregation(authorAgg)
        .build();
    
    // 4. 執(zhí)行查詢
    SearchHits<Article> hits = operations.search(searchQuery, Article.class);
    ParsedStringTerms authorStats = hits.getAggregations().get("author_stats");
    
    // 5. 處理結(jié)果
    Map<String, Long> stats = new LinkedHashMap<>();
    for (Terms.Bucket bucket : authorStats.getBuckets()) {
        String author = bucket.getKeyAsString();
        long count = bucket.getDocCount();
        double avgViews = ((ParsedAvg) bucket.getAggregations().get("avg_views")).getValue();
        
        stats.put(author + " (平均閱讀量: " + String.format("%.1f", avgViews) + ")", count);
    }
    
    return stats;
}

聚合類型對比表

聚合類型對應(yīng)類作用描述示例場景
指標(biāo)聚合AvgAggregationBuilder計算平均值計算平均閱讀量
SumAggregationBuilder計算總和計算總閱讀量
Max/MinAggregationBuilder最大/最小值查找最熱/最早文章
桶聚合TermsAggregationBuilder按字段值分組按作者分組統(tǒng)計文章數(shù)
DateHistogramAggregation按時間間隔分組按月統(tǒng)計文章發(fā)布量
RangeAggregationBuilder按自定義范圍分組按閱讀量分段統(tǒng)計
管道聚合DerivativePipelineAgg計算派生值計算閱讀量增長率

3.2.2 嵌套聚合與多級分析

public void nestedAggregationExample() {
    // 1. 構(gòu)建日期直方圖聚合
    DateHistogramAggregationBuilder dateHistogram = AggregationBuilders.dateHistogram("by_publish_date")
        .field("publishTime")
        .calendarInterval(DateHistogramInterval.MONTH)  // 按月分組
        .format("yyyy-MM")                             // 格式化為年月
        .minDocCount(1);                               // 至少1個文檔才顯示
    
    // 2. 添加子聚合(按作者分組)
    TermsAggregationBuilder authorTerms = AggregationBuilders.terms("by_author")
        .field("author.keyword")
        .size(5);
    
    // 3. 在作者分組下再添加子聚合(平均閱讀量)
    authorTerms.subAggregation(AggregationBuilders.avg("avg_views").field("viewCount"));
    dateHistogram.subAggregation(authorTerms);
    
    // 4. 構(gòu)建查詢
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .addAggregation(dateHistogram)
        .build();
    
    // 5. 執(zhí)行并解析結(jié)果
    SearchHits<Article> hits = operations.search(query, Article.class);
    ParsedDateHistogram dateHistogramAgg = hits.getAggregations().get("by_publish_date");
    
    for (Histogram.Bucket dateBucket : dateHistogramAgg.getBuckets()) {
        String date = dateBucket.getKeyAsString();
        System.out.println("\n月份: " + date);
        
        ParsedStringTerms authorAgg = dateBucket.getAggregations().get("by_author");
        for (Terms.Bucket authorBucket : authorAgg.getBuckets()) {
            String author = authorBucket.getKeyAsString();
            double avgViews = ((ParsedAvg) authorBucket.getAggregations().get("avg_views")).getValue();
            
            System.out.printf("  作者: %-15s 文章數(shù): %-5d 平均閱讀量: %.1f\n", 
                author, authorBucket.getDocCount(), avgViews);
        }
    }
}

聚合結(jié)果可視化示例

barChart
    title 各月份文章統(tǒng)計
    xAxis 月份
    yAxis 文章數(shù)
    series "技術(shù)達(dá)人"
    series "架構(gòu)師"
    
    "2023-01": 15, 8
    "2023-02": 12, 10
    "2023-03": 18, 15

四、性能優(yōu)化與生產(chǎn)實(shí)踐

4.1 索引設(shè)計與性能調(diào)優(yōu)

4.1.1 索引生命周期管理

// 創(chuàng)建索引時指定生命周期策略
@Configuration
public class ElasticsearchConfig {
    
    @Bean
    public IndexOperations indexOperations(ElasticsearchOperations operations) {
        IndexOperations indexOps = operations.indexOps(Article.class);
        
        // 定義生命周期策略
        Map<String, Object> lifecyclePolicy = new HashMap<>();
        lifecyclePolicy.put("policy", new HashMap<String, Object>() {{
            put("phases", new HashMap<String, Object>() {{
                put("hot", new HashMap<String, Object>() {{
                    put("actions", new HashMap<String, Object>() {{
                        put("rollover", new HashMap<String, Object>() {{
                            put("max_size", "50GB");
                            put("max_age", "30d");
                        }});
                    }});
                }});
                put("delete", new HashMap<String, Object>() {{
                    put("min_age", "365d");
                    put("actions", new HashMap<String, Object>() {{
                        put("delete", new HashMap<>());
                    }});
                }});
            }});
        }});
        
        // 創(chuàng)建索引設(shè)置
        Settings settings = Settings.builder()
            .put("index.lifecycle.name", "article_policy")
            .put("index.lifecycle.rollover_alias", "blog_articles")
            .build();
        
        indexOps.create(settings, indexOps.createMapping(Article.class));
        return indexOps;
    }
}

索引生命周期階段說明

階段觸發(fā)條件典型操作存儲類型建議
Hot新索引頻繁寫入和查詢SSD/NVMe
Warm數(shù)據(jù)變冷(如7天后)只讀,偶爾查詢HDD
Cold很少訪問(如30天后)歸檔,極少查詢歸檔存儲
Delete超過保留期(如1年后)刪除索引釋放空間-

4.1.2 索引優(yōu)化策略

1. 分片策略優(yōu)化

// 動態(tài)調(diào)整索引設(shè)置
indexOps.putSettings(Settings.builder()
    .put("index.number_of_shards", 5)        // 根據(jù)數(shù)據(jù)量調(diào)整
    .put("index.number_of_replicas", 1)      // 生產(chǎn)環(huán)境建議≥1
    .put("index.refresh_interval", "30s")    // 降低刷新頻率提高寫入性能
    .build());

分片數(shù)量計算參考公式

分片數(shù) = max(數(shù)據(jù)節(jié)點(diǎn)數(shù) × 1.5, 日志類數(shù)據(jù)總大小/30GB, 搜索類數(shù)據(jù)總大小/50GB)

2. 字段映射優(yōu)化

@Field(type = FieldType.Text, 
       analyzer = "ik_max_word",
       searchAnalyzer = "ik_smart",
       fielddata = false,          // 避免對text字段啟用fielddata
       norms = false,             // 如果不關(guān)心評分可以禁用
       indexOptions = IndexOptions.DOCS)  // 只索引文檔不存儲頻率和位置
private String title;

字段類型選擇指南

數(shù)據(jù)類型適用場景是否分詞是否支持聚合存儲開銷
text全文檢索內(nèi)容不直接支持
keyword精確值匹配/聚合支持
long/double數(shù)值范圍查詢/聚合支持
date時間范圍查詢支持
boolean是/否過濾支持最低
nested對象數(shù)組獨(dú)立查詢-支持

4.2 查詢性能優(yōu)化

4.2.1 查詢DSL優(yōu)化技巧

// 優(yōu)化前的查詢
NativeSearchQuery slowQuery = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.matchQuery("content", "Spring Boot"))
    .build();

// 優(yōu)化后的查詢
NativeSearchQuery fastQuery = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.boolQuery()
        .must(QueryBuilders.matchQuery("title", "Spring Boot").boost(2.0f))
        .should(QueryBuilders.matchQuery("content", "Spring Boot"))
        .minimumShouldMatch(1))
    .withSourceFilter(new FetchSourceFilter(
        new String[]{"id", "title", "author", "publishTime"},  // 只返回必要字段
        null))
    .withPageable(PageRequest.of(0, 20, Sort.by("publishTime").descending()))
    .build();

查詢優(yōu)化對照表

優(yōu)化點(diǎn)優(yōu)化前優(yōu)化后性能提升原因
查詢類型簡單match查詢bool組合查詢更精確的控制匹配邏輯
字段選擇查詢所有字段只查詢必要字段減少網(wǎng)絡(luò)傳輸和序列化開銷
分頁控制默認(rèn)分頁(通常10條)明確指定分頁大小避免意外的大結(jié)果集
排序字段無排序或_score排序使用已索引的日期字段排序避免內(nèi)存消耗大的評分排序
字段數(shù)據(jù)訪問對text字段進(jìn)行聚合使用.keyword子字段聚合避免啟用昂貴的fielddata機(jī)制

4.2.2 緩存策略與批量操作

// 批量插入優(yōu)化
@Autowired
private ElasticsearchOperations operations;

public void bulkInsert(List<Article> articles) {
    List<IndexQuery> queries = articles.stream()
        .map(article -> new IndexQueryBuilder()
            .withId(article.getId())
            .withObject(article)
            .build())
        .collect(Collectors.toList());
    
    // 執(zhí)行批量操作
    operations.bulkIndex(queries, Article.class);
    
    // 手動刷新(通常不需要,根據(jù)業(yè)務(wù)需求)
    operations.indexOps(Article.class).refresh();
}

// 使用緩存優(yōu)化熱點(diǎn)查詢
@Cacheable(value = "articleCache", key = "#id")
public Article findByIdWithCache(String id) {
    return repository.findById(id).orElse(null);
}

批量操作性能對比

操作方式1000文檔耗時(ms)內(nèi)存占用(MB)適用場景
單條插入450050初始化數(shù)據(jù)或?qū)崟r插入
批量(100條)120080常規(guī)批量操作
批量(500條)800150大數(shù)據(jù)量導(dǎo)入
并行批量500200極高性能要求場景

五、實(shí)戰(zhàn)案例:博客搜索系統(tǒng)

5.1 需求分析與設(shè)計

系統(tǒng)需求

  • 支持文章標(biāo)題、內(nèi)容、作者的多字段搜索
  • 實(shí)現(xiàn)相關(guān)度排序和篩選功能
  • 提供熱門標(biāo)簽和作者統(tǒng)計
  • 支持搜索建議和拼寫糾正
  • 需要高亮顯示匹配內(nèi)容

數(shù)據(jù)模型設(shè)計

5.2 完整實(shí)現(xiàn)代碼

5.2.1 搜索服務(wù)實(shí)現(xiàn)

@Service
public class BlogSearchService {
    
    @Autowired
    private ElasticsearchOperations operations;
    
    @Autowired
    private ArticleRepository repository;
    
    public SearchResult<Article> searchArticles(SearchRequest request) {
        // 1. 構(gòu)建基礎(chǔ)查詢
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        // 關(guān)鍵詞查詢(多字段匹配)
        if (StringUtils.isNotBlank(request.getKeyword())) {
            boolQuery.must(QueryBuilders.multiMatchQuery(request.getKeyword(), 
                    "title^2", "content", "author")  // 標(biāo)題權(quán)重加倍
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)  // 最佳字段匹配
                .tieBreaker(0.3f));  // 字段間相關(guān)性平衡
        }
        
        // 作者過濾
        if (StringUtils.isNotBlank(request.getAuthor())) {
            boolQuery.filter(QueryBuilders.termQuery("author.keyword", request.getAuthor()));
        }
        
        // 標(biāo)簽過濾
        if (request.getTags() != null && !request.getTags().isEmpty()) {
            boolQuery.filter(QueryBuilders.termsQuery("tags.keyword", request.getTags()));
        }
        
        // 日期范圍
        if (request.getStartDate() != null || request.getEndDate() != null) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("publishTime");
            if (request.getStartDate() != null) {
                rangeQuery.gte(request.getStartDate());
            }
            if (request.getEndDate() != null) {
                rangeQuery.lte(request.getEndDate());
            }
            boolQuery.filter(rangeQuery);
        }
        
        // 2. 構(gòu)建高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder()
            .field("title").preTags("<em class='highlight'>").postTags("</em>")
            .field("content").fragmentSize(200).numOfFragments(3);
        
        // 3. 構(gòu)建完整查詢
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withPageable(PageRequest.of(request.getPage(), request.getSize()))
            .withSort(buildSort(request.getSortType()))
            .withHighlightBuilder(highlightBuilder)
            .build();
        
        // 4. 執(zhí)行查詢
        SearchHits<Article> hits = operations.search(searchQuery, Article.class);
        
        // 5. 處理結(jié)果
        List<Article> articles = new ArrayList<>();
        for (SearchHit<Article> hit : hits) {
            Article article = hit.getContent();
            // 處理高亮
            if (hit.getHighlightFields().containsKey("title")) {
                article.setTitle(hit.getHighlightFields().get("title").get(0));
            }
            if (hit.getHighlightFields().containsKey("content")) {
                article.setContent(String.join("...", hit.getHighlightFields().get("content")));
            }
            articles.add(article);
        }
        
        // 6. 返回分頁結(jié)果
        return new SearchResult<>(
            articles,
            hits.getTotalHits(),
            request.getPage(),
            request.getSize(),
            hits.getMaxScore()
        );
    }
    
    private SortBuilder<?> buildSort(String sortType) {
        switch (sortType) {
            case "newest":
                return SortBuilders.fieldSort("publishTime").order(SortOrder.DESC);
            case "hottest":
                return SortBuilders.fieldSort("viewCount").order(SortOrder.DESC);
            case "most_commented":
                return SortBuilders.scriptSort(
                    ScriptBuilders.script("doc['comments'].size()"), 
                    ScriptSortBuilder.ScriptSortType.NUMBER).order(SortOrder.DESC);
            default:  // 默認(rèn)按相關(guān)度
                return SortBuilders.scoreSort();
        }
    }
    
    // 搜索建議實(shí)現(xiàn)
    public List<String> suggestTitles(String prefix) {
        CompletionSuggestionBuilder suggestionBuilder = SuggestBuilders
            .completionSuggestion("title_suggest")
            .prefix(prefix)
            .size(5);
        
        SearchRequest request = new SearchRequest("blog_articles")
            .source(new SearchSourceBuilder()
                .suggest(new SuggestBuilder().addSuggestion("title_suggest", suggestionBuilder)));
        
        try {
            SearchResponse response = operations.getClient().search(request, RequestOptions.DEFAULT);
            return Arrays.stream(response.getSuggest()
                    .getSuggestion("title_suggest")
                    .getEntries().get(0)
                    .getOptions())
                .map(Suggest.Suggestion.Entry.Option::getText)
                .collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException("搜索建議失敗", e);
        }
    }
}

5.2.2 控制器層實(shí)現(xiàn)

@RestController
@RequestMapping("/api/articles")
public class ArticleController {
    
    @Autowired
    private BlogSearchService searchService;
    
    @GetMapping("/search")
    public ResponseEntity<SearchResult<Article>> search(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) String author,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
            @RequestParam(required = false) List<String> tags,
            @RequestParam(defaultValue = "relevance") String sort,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        
        SearchRequest request = new SearchRequest(
            keyword, author, tags, startDate, endDate, sort, page, size);
        
        return ResponseEntity.ok(searchService.searchArticles(request));
    }
    
    @GetMapping("/suggest")
    public ResponseEntity<List<String>> suggestTitles(@RequestParam String prefix) {
        return ResponseEntity.ok(searchService.suggestTitles(prefix));
    }
    
    @GetMapping("/stats/authors")
    public ResponseEntity<Map<String, Long>> authorStats() {
        return ResponseEntity.ok(searchService.authorArticleStats(null));
    }
}

5.3 系統(tǒng)擴(kuò)展與高級功能

5.3.1 同義詞搜索擴(kuò)展

// 同義詞分析器配置
@Configuration
public class SynonymConfig {
    
    @Bean
    public AnalysisPlugin analysisPlugin() {
        return new AnalysisPlugin() {
            @Override
            public List<PreConfiguredTokenFilter> getPreConfiguredTokenFilters() {
                return List.of(
                    PreConfiguredTokenFilter.singleton("synonym_filter", 
                        () -> new SynonymGraphTokenFilterFactory(
                            new IndexSettings(IndexModule.newIndexSettings(
                                IndexMetadata.builder("_na_").settings(Settings.builder()
                                    .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
                                    .build()).build(), Settings.EMPTY),
                                new AnalysisRegistry(null, null, null, null, null, null, null, null, null, null)),
                            null, "synonym_filter", Settings.builder()
                                .put("synonyms_path", "analysis/synonyms.txt")
                                .put("expand", "true")
                                .build())));
            }
        };
    }
}

// 實(shí)體類中使用同義詞分析器
@Field(type = FieldType.Text, 
       analyzer = "synonym_analyzer",
       searchAnalyzer = "synonym_analyzer")
private String content;

同義詞文件示例(analysis/synonyms.txt)

Spring Boot, SB
微服務(wù) => 分布式系統(tǒng)
ES, Elasticsearch

5.3.2 個性化搜索推薦

public List<Article> recommendArticles(String userId, String currentArticleId) {
    // 1. 獲取用戶歷史行為
    List<UserBehavior> behaviors = behaviorService.getUserBehaviors(userId);
    
    // 2. 構(gòu)建個性化查詢
    BoolQueryBuilder query = QueryBuilders.boolQuery()
        .mustNot(QueryBuilders.idsQuery().addIds(currentArticleId))  // 排除當(dāng)前文章
        
    // 基于標(biāo)簽的推薦
    if (!behaviors.isEmpty()) {
        List<String> preferredTags = extractPreferredTags(behaviors);
        query.should(QueryBuilders.termsQuery("tags.keyword", preferredTags).boost(2.0f));
    }
    
    // 基于作者的推薦
    List<String> preferredAuthors = extractPreferredAuthors(behaviors);
    if (!preferredAuthors.isEmpty()) {
        query.should(QueryBuilders.termsQuery("author.keyword", preferredAuthors).boost(1.5f));
    }
    
    // 3. 添加熱門度因素
    query.should(QueryBuilders.rangeQuery("viewCount")
        .gte(1000)
        .boost(0.5f));
    
    // 4. 執(zhí)行查詢
    NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
        .withQuery(query)
        .withPageable(PageRequest.of(0, 5))
        .build();
    
    return operations.search(searchQuery, Article.class)
        .stream()
        .map(SearchHit::getContent)
        .collect(Collectors.toList());
}

六、常見問題與解決方案

6.1 集成問題排查

6.1.1 版本兼容性問題

Spring Boot與Elasticsearch客戶端版本對應(yīng)關(guān)系:

Spring Boot版本Spring Data ES版本官方推薦ES服務(wù)端版本重要注意事項
2.4.x4.0.x7.9.x最后一個支持TransportClient的版本
2.5.x4.1.x7.12.x開始默認(rèn)使用High Level REST Client
2.6.x4.2.x7.15.x移除TransportClient支持
2.7.x4.3.x7.17.x性能優(yōu)化和新特性支持
3.0.x5.0.x8.5.x需要Java 17+

版本沖突解決方案

明確版本依賴:

<properties>
    <elasticsearch.version>7.17.3</elasticsearch.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>${elasticsearch.version}</version>
    </dependency>
</dependencies>

使用兼容的Repository配置:

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
    
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        return new RestHighLevelClient(
            RestClient.builder(HttpHost.create("http://localhost:9200"))
                .setRequestConfigCallback(builder -> 
                    builder.setConnectTimeout(5000).setSocketTimeout(60000))
                .setHttpClientConfigCallback(httpClientBuilder -> 
                    httpClientBuilder.setDefaultCredentialsProvider(
                        new BasicCredentialsProvider() {{
                            setCredentials(AuthScope.ANY, 
                                new UsernamePasswordCredentials("elastic", "password"));
                        }})));
    }
}

6.1.2 連接問題診斷

常見連接錯誤及解決方案

錯誤類型可能原因解決方案
NoNodeAvailableException集群不可達(dá)或網(wǎng)絡(luò)問題檢查ES服務(wù)狀態(tài),驗證spring.elasticsearch.uris配置,檢查防火墻設(shè)置
ElasticsearchStatusException認(rèn)證失敗或權(quán)限不足驗證用戶名/密碼,檢查用戶角色權(quán)限
SocketTimeoutException查詢超時增加socket-timeout配置,優(yōu)化復(fù)雜查詢
JsonParseException響應(yīng)解析失敗檢查實(shí)體類映射,確保與ES文檔結(jié)構(gòu)匹配
VersionConflictException文檔版本沖突(樂觀鎖)實(shí)現(xiàn)重試機(jī)制或獲取最新版本

診斷工具類

@Component
public class ElasticsearchHealthChecker {
    
    @Autowired
    private RestHighLevelClient client;
    
    public Health checkClusterHealth() {
        try {
            ClusterHealthResponse response = client.cluster()
                .health(new ClusterHealthRequest(), RequestOptions.DEFAULT);
            
            return Health.status(response.getStatus().name())
                .withDetail("cluster_name", response.getClusterName())
                .withDetail("node_count", response.getNumberOfNodes())
                .withDetail("active_shards", response.getActiveShards())
                .build();
        } catch (IOException e) {
            return Health.down(e).build();
        }
    }
    
    public boolean testConnection() {
        try {
            return client.ping(RequestOptions.DEFAULT);
        } catch (IOException e) {
            return false;
        }
    }
}

6.2 性能問題優(yōu)化

6.2.1 索引性能瓶頸

寫入性能優(yōu)化方案

  • 批量處理
// 批量插入優(yōu)化示例
public void bulkInsertArticles(List<Article> articles) {
    List<IndexQuery> queries = articles.stream()
        .map(article -> new IndexQueryBuilder()
            .withId(article.getId())
            .withObject(article)
            .build())
        .collect(Collectors.toList());
    
    // 配置批量參數(shù)
    BulkOptions options = BulkOptions.builder()
        .withTimeout(Duration.ofMinutes(2))
        .withRefreshPolicy(RefreshPolicy.NONE)  // 不立即刷新
        .build();
    
    operations.bulkIndex(queries, options, Article.class);
}
  • 服務(wù)器端優(yōu)化參數(shù)
// 創(chuàng)建索引時優(yōu)化設(shè)置
Settings settings = Settings.builder()
    .put("index.refresh_interval", "30s")       // 降低刷新頻率
    .put("index.translog.sync_interval", "5s")  // 事務(wù)日志同步間隔
    .put("index.number_of_replicas", 0)        // 初始加載時禁用副本
    .build();

indexOps.create(settings, indexOps.createMapping(Article.class));

寫入性能影響因素表

因素影響程度優(yōu)化建議
刷新間隔增大refresh_interval(默認(rèn)1s)
副本數(shù)初始導(dǎo)入時設(shè)為0,完成后恢復(fù)
批量大小每批5-15MB,根據(jù)文檔大小調(diào)整
硬件配置極高使用SSD,增加內(nèi)存和CPU核心數(shù)
索引緩沖區(qū)增加indices.memory.index_buffer_size
合并策略優(yōu)化index.merge策略

6.2.2 查詢性能調(diào)優(yōu)

慢查詢分析工具

// 啟用慢查詢?nèi)罩?
indexOps.putSettings(Settings.builder()
    .put("index.search.slowlog.threshold.query.warn", "10s")
    .put("index.search.slowlog.threshold.query.info", "5s")
    .put("index.search.slowlog.threshold.fetch.warn", "1s")
    .put("index.search.slowlog.threshold.fetch.info", "500ms")
    .build());

查詢優(yōu)化檢查清單

索引設(shè)計優(yōu)化

  • 使用合適的數(shù)據(jù)類型(避免對text字段排序/聚合)
  • 合理設(shè)置分片數(shù)(避免過度分片)
  • 對熱點(diǎn)字段使用doc_values

查詢DSL優(yōu)化

  • 使用filter上下文替代query上下文緩存結(jié)果
  • 避免通配符查詢(特別是前綴通配符)
  • 限制返回字段(_source filtering)
  • 使用search_after替代深度分頁

硬件與JVM優(yōu)化

  • 給ES分配不超過50%的物理內(nèi)存
  • 使用更快的存儲設(shè)備(SSD/NVMe)
  • 調(diào)整文件系統(tǒng)緩存(Linux的swappiness)

性能對比案例

優(yōu)化前查詢(耗時1200ms):

{
  "query": {
    "match": {
      "content": "Spring Boot"
    }
  },
  "size": 100
}

優(yōu)化后查詢(耗時200ms):

{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": {"query": "Spring Boot", "boost": 2}}},
        {"match": {"content": "Spring Boot"}}
      ],
      "filter": [
        {"range": {"publishTime": {"gte": "now-1y/d"}}}
      ]
    }
  },
  "_source": ["id", "title", "author", "publishTime"],
  "size": 20,
  "sort": [{"viewCount": "desc"}]
}

七、未來發(fā)展與技術(shù)展望

7.1 Elasticsearch 8.x新特性

7.1.1 向量搜索支持

// 向量字段映射
@Field(type = FieldType.Dense_Vector, dims = 512)
private float[] titleVector;

// 向量搜索實(shí)現(xiàn)
public List<Article> vectorSearch(float[] queryVector, int k) {
    KnnSearchBuilder knnSearch = new KnnSearchBuilder("titleVector", queryVector, k)
        .boost(0.9f);
    
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withKnnSearch(knnSearch)
        .build();
    
    return operations.search(query, Article.class)
        .stream()
        .map(SearchHit::getContent)
        .collect(Collectors.toList());
}

7.1.2 新的安全模型

Elasticsearch 8.x的安全增強(qiáng):

  • 默認(rèn)啟用TLS加密
  • 更簡單的安全配置
  • 增強(qiáng)的角色訪問控制

配置示例:

spring:
  elasticsearch:
    uris: "https://localhost:9200"
    ssl:
      verification-mode: certificate  # 嚴(yán)格證書驗證
    username: "elastic"
    password: "yourpassword"
    socket-keep-alive: true          # 保持長連接

7.2 云原生趨勢下的演進(jìn)

7.2.1 Kubernetes部署優(yōu)化

Elasticsearch在K8s中的最佳實(shí)踐:

使用官方ECK(Elastic Cloud on Kubernetes)Operator

合理的資源請求與限制:

resources:
  requests:
    memory: "4Gi"
    cpu: "2"
  limits:
    memory: "8Gi"
    cpu: "4"

分布式存儲配置:

volumeClaimTemplates:
  - metadata:
      name: elasticsearch-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "ssd"
      resources:
        requests:
          storage: "100Gi"

7.2.2 Serverless架構(gòu)下的搜索

無服務(wù)器架構(gòu)中的搜索模式:

  • 使用Elasticsearch Service的Serverless產(chǎn)品
  • 事件驅(qū)動的索引更新:
// AWS Lambda示例
public class IndexUpdater implements RequestHandler<S3Event, Void> {
    
    private final ElasticsearchOperations operations = createElasticsearchTemplate();
    
    public Void handleRequest(S3Event event, Context context) {
        event.getRecords().forEach(record -> {
            String key = record.getS3().getObject().getKey();
            String content = downloadContentFromS3(key);
            Article article = parseContent(content);
            operations.save(article);
        });
        return null;
    }
}

7.3 與其他技術(shù)的融合

7.3.1 與機(jī)器學(xué)習(xí)結(jié)合

// 使用ML模型生成搜索排名
public SearchResult<Article> smartSearch(String query, User user) {
    // 1. 基礎(chǔ)相關(guān)性搜索
    SearchResult<Article> baseResults = basicSearch(query);
    
    // 2. 應(yīng)用機(jī)器學(xué)習(xí)排序
    List<Article> rankedResults = mlRankingService.rerank(
        baseResults.getContent(),
        query,
        user.getPreferences());
    
    // 3. 返回重新排序的結(jié)果
    return new SearchResult<>(
        rankedResults,
        baseResults.getTotalElements(),
        baseResults.getPage(),
        baseResults.getSize(),
        baseResults.getMaxScore()
    );
}

7.3.2 多模態(tài)搜索實(shí)現(xiàn)

// 圖像+文本混合搜索
public List<Article> multimodalSearch(String textQuery, byte[] image) {
    // 1. 提取圖像特征向量
    float[] imageVector = imageService.extractFeatures(image);
    
    // 2. 構(gòu)建混合查詢
    QueryBuilder textQueryBuilder = QueryBuilders.matchQuery("content", textQuery);
    KnnSearchBuilder imageKnn = new KnnSearchBuilder("image_vector", imageVector, 10);
    
    // 3. 執(zhí)行混合搜索
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(textQueryBuilder)
        .withKnnSearch(imageKnn)
        .build();
    
    return operations.search(query, Article.class)
        .stream()
        .map(SearchHit::getContent)
        .collect(Collectors.toList());
}

通過本指南的系統(tǒng)學(xué)習(xí),您應(yīng)該已經(jīng)掌握了Spring Boot集成Elasticsearch從基礎(chǔ)到高級的全套技術(shù)棧。實(shí)際應(yīng)用中,請根據(jù)具體業(yè)務(wù)需求選擇合適的集成方式和優(yōu)化策略,并持續(xù)關(guān)注Elasticsearch生態(tài)的最新發(fā)展。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Spring中的Bean對象初始化詳解

    Spring中的Bean對象初始化詳解

    這篇文章主要介紹了Spring中的Bean對象初始化詳解,我們也可以根據(jù)Java語言的特性猜測到其很有可能是通過反射機(jī)制來完成Bean的初始化操作,接下來我們一步一步的剖析Spring對Bean的初始化操作,需要的朋友可以參考下
    2023-12-12
  • Java StringBuilder和StringBuffer源碼分析

    Java StringBuilder和StringBuffer源碼分析

    這篇文章主要針對Java中兩個常用的操作字符串的類 StringBuilder和StringBuffer進(jìn)行源碼分析,感興趣的小伙伴們可以參考一下
    2016-01-01
  • 詳解java中jvm虛擬機(jī)棧的作用

    詳解java中jvm虛擬機(jī)棧的作用

    這篇文章主要介紹了jvm虛擬機(jī)棧的作用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • SpringBoot異步線程父子線程數(shù)據(jù)傳遞的5種方式

    SpringBoot異步線程父子線程數(shù)據(jù)傳遞的5種方式

    這篇文章主要介紹了SpringBoot異步線程父子線程數(shù)據(jù)傳遞的5種方式,文中通過代碼示例給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-08-08
  • JPA自定義對象接收查詢結(jié)果集操作

    JPA自定義對象接收查詢結(jié)果集操作

    這篇文章主要介紹了JPA自定義對象接收查詢結(jié)果集操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • Java 獲取當(dāng)前設(shè)備的 IP 地址(最新推薦)

    Java 獲取當(dāng)前設(shè)備的 IP 地址(最新推薦)

    Internet 協(xié)議 (IP) 地址可以是連接到 TCP/IP 網(wǎng)絡(luò)的每個設(shè)備的標(biāo)識符,該標(biāo)識符用于識別和定位中間通信的節(jié)點(diǎn),這篇文章主要介紹了在 Java 中獲取當(dāng)前設(shè)備的 IP 地址,需要的朋友可以參考下
    2023-06-06
  • springboot實(shí)現(xiàn)上傳并解析Excel過程解析

    springboot實(shí)現(xiàn)上傳并解析Excel過程解析

    這篇文章主要介紹了springboot實(shí)現(xiàn)上傳并解析Excel過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • Mybatis或Mybatis-Plus框架的xml文件中特殊符號的使用詳解

    Mybatis或Mybatis-Plus框架的xml文件中特殊符號的使用詳解

    這篇文章主要介紹了Mybatis或Mybatis-Plus框架的xml文件中特殊符號的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Spring MVC如何設(shè)置響應(yīng)

    Spring MVC如何設(shè)置響應(yīng)

    本文介紹了如何在Spring框架中設(shè)置響應(yīng),并通過不同的注解返回靜態(tài)頁面、HTML片段和JSON數(shù)據(jù),此外,還講解了如何設(shè)置響應(yīng)的狀態(tài)碼和Header
    2025-01-01
  • 詳談Java靜態(tài)動態(tài)的問題

    詳談Java靜態(tài)動態(tài)的問題

    下面小編就為大家?guī)硪黄斦凧ava靜態(tài)動態(tài)的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09

最新評論

肃宁县| 济宁市| 台中市| 岳西县| 会昌县| 泸定县| 新巴尔虎右旗| 山东| 任丘市| 怀安县| 景德镇市| 邮箱| 舒城县| 南丹县| 马尔康县| 大名县| 南汇区| 金乡县| 耒阳市| 潞城市| 鲜城| 涟水县| 伊金霍洛旗| 贺州市| 会东县| 江油市| 沈丘县| 方正县| 霸州市| 华阴市| 西平县| 公主岭市| 内丘县| 金沙县| 望江县| 黎城县| 高陵县| 扶风县| 驻马店市| 湖州市| 隆子县|