springboot整合ElasticSearch詳細(xì)

一、安裝Elasticsearch相關(guān)插件

為了避免使用的Elasticsearch版本和SpringBoot采用的版本不一致導(dǎo)致的問題,盡量使用一致的版本。下表是對應(yīng)關(guān)系:

image.png
安裝Elasticsearch

Elasticsearch官網(wǎng),點(diǎn)擊跳轉(zhuǎn)即可
1、下載上面鏈接的安裝包
2、解壓到任意目錄
3、啟動es /bin/elasticsearch.bat
4、查看安裝結(jié)果,在網(wǎng)頁輸入localhost:9200,出現(xiàn)下圖即為成功

image.png

接下來我們要通過修改Elasticsearch安裝目錄下的/config/elasticsearch.yml處理一些問題

解決IP無法進(jìn)行訪問

#增加IP訪問配置
network.bind_host: 0.0.0.0
#注釋此配置
#cluster.initial_master_nodes: ["node-1", "node-2"]
#修改為單節(jié)點(diǎn)配置
cluster.initial_master_nodes: ["node-1"]

解決跨域問題
由于前后端分離開發(fā),所以會存在跨域問題,需要在服務(wù)端做CORS的配置。

#開啟跨域支持
http.cors.enabled: true
#允許所有人跨域訪問
http.cors.allow-origin: "*"
#瀏覽器允許在跨域請求中發(fā)送憑證(如 cookies、HTTP 認(rèn)證信息等)
http.cors.allow-credentials: true
#允許特定的請求頭
http.cors.allow-headers: Authorization,X-Requested-With,Content-Length,Content-Type

開啟集群身份認(rèn)證與用戶鑒權(quán)和集群內(nèi)部安全通信

# 集群身份認(rèn)證與用戶鑒權(quán)
xpack.security.enabled: true
# 集群內(nèi)部安全通信
xpack.license.self_generated.type: basic
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: D:\elasticsearch\elasticsearch-7.6.2\config\elastic-stack-ca.p12
xpack.security.transport.ssl.truststore.path: D:\elasticsearch\elasticsearch-7.6.2\config\elastic-stack-ca.p12

重啟Elasticsearch服務(wù)以使更改生效

Elasticsearch 的默認(rèn)賬戶為 elastic 默認(rèn)密碼為 changme
一、通過Kibana更改密碼
1、登錄到Kibana。
2、導(dǎo)航到“Management”(管理)> “Security”(安全)> “Users”(用戶)。
3、找到你想要修改密碼的用戶,點(diǎn)擊“Edit”(編輯)。
4、在“Change Password”(更改密碼)部分,輸入新密碼,然后點(diǎn)擊“Update”(更新)。

二、通過Elasticsearch REST API更改密碼

curl -u elastic:your_current_password -X POST "localhost:9200/_security/user/username/_password" -H 'Content-Type: application/json' -d'
{
  "password" : "new_password"
}

替換your_current_password、username和new_password為實(shí)際的當(dāng)前密碼、用戶名和新密碼。

安裝es-head插件,方便查看ES中的索引及數(shù)據(jù)

安裝es-head插件需要把node和grunt配置好
node下載地址下載對應(yīng)環(huán)境的node版本安裝即可。
安裝過程結(jié)束后,在dos窗口查看是否安裝成功,使用命令:node -v,出現(xiàn)如下截圖,則說明安裝成功

image.png

在node安裝路徑下,使用命令安裝:npm install -g grunt-cli 安裝grunt。 安裝結(jié)束后,使用命令grunt-version查看是否安裝成功,出現(xiàn)如下截圖,說明安裝成功。
image.png

es-head下載地址
解壓elasticsearch-head-master
在該目錄下進(jìn)入cmd命令,執(zhí)行npm install
image.png

執(zhí)行完成后運(yùn)行命令 grunt server,
grunt server是啟動命令
如果出現(xiàn)報錯
image.png

則使用 cmd繼續(xù)執(zhí)行npm install grunt --save-dev。 這將向package.json添加最新版本。
image.png

如果不報錯,則命令窗顯示
image.png

瀏覽器輸入127.0.0.1:9100
image.png

安裝kibana,用途:便于通過rest api調(diào)試ES。

kibana官方下載地址
1、解壓
2、修改 kibana-7.12.0-windows-x86_64/config/kibana.yml 32行
3、改為elasticsearch.hosts: [“http://127.0.0.1:9200”]
4、保存之后,運(yùn)行bin/kibana.bat
5、瀏覽器中訪問kibana開發(fā)工具:http://localhost:5601/app/kibana#/dev_tools/console

image.png

如果想使用ip訪問kibana,需要修改 kibana-7.12.0-windows-x86_64/config/kibana.yml 7行
改為 server.host: "0.0.0.0"
如果想使用kibana漢化 需要修改 kibana-7.12.0-windows-x86_64/config/kibana.yml 最后一行
i18n.locale: "zh-CN"

安裝ik分詞器,注意:下載的ik分詞器版本號要和安裝的elasticsearch版本一致

把下載的ik分詞器解壓至Elasticsearch的安裝目錄/plugins/ik內(nèi)。


image.png
image.png
  1. 測試ik分詞器
  2. 重啟elasticsearch
  3. 重啟kibana
  4. 進(jìn)入kibana的開發(fā)工具中執(zhí)行命令測試 開發(fā)工具

ik_max_wordik_smart 是 IK 分詞器的兩種分詞模式,主要用于中文分詞。以下是它們的區(qū)別:

  1. ik_max_word

    • 特點(diǎn): 會將文本進(jìn)行最細(xì)粒度的拆分,盡可能多地拆出詞語。
    • 適用場景: 當(dāng)需要盡可能多的關(guān)鍵詞匹配時使用,例如搜索引擎中的寬泛查詢。
    • 示例: 對于字符串“中華人民共和國國歌”,ik_max_word 會拆分為:["中華人民共和國", "中華人民", "中華", "華人", "人民共和國", "人民", "共和國", "共和", "國", "國歌"]
      image.png
  2. ik_smart

    • 特點(diǎn): 會以更智能的方式對文本進(jìn)行拆分,盡量減少冗余詞語,保留語義完整。
    • 適用場景: 當(dāng)需要更精確的分詞結(jié)果時使用,適合對查詢性能要求較高的場景。
    • 示例: 對于字符串“中華人民共和國國歌”,ik_smart 會拆分為:["中華人民共和國", "國歌"]
      image.png

我個人建議是:

  • analyzer = "ik_max_word" 表示在索引階段使用 ik_max_word 模式進(jìn)行分詞。
  • searchAnalyzer = "ik_smart" 表示在搜索階段使用 ik_smart 模式進(jìn)行分詞。
    這種配置可以讓索引階段盡可能多地提取關(guān)鍵詞,而在搜索階段提供更精準(zhǔn)的匹配結(jié)果。

二、整合SpringBoot和Elasticearch

引入依賴

 <!-- Spring Boot 對 Elasticsearch 的啟動器,提供在 Spring Boot 應(yīng)用中方便地使用 Elasticsearch 的方式。 -->
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </dependency>

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

yml文件增加配置

spring:
  # elasticsearch配置
  elasticsearch:
    rest:
      # 用戶名
      username: elastic
      # 密碼
      password: 123456
      # 服務(wù)器地址,支持多個uri,用','隔開
      uris: localhost:9200
      # 連接超時時間
      connection-timeout: 10s
      # 讀取超時時間
      read-timeout: 30s
      # 最大連接數(shù)
      max-connect-requests: 10
      # 空閑連接的最大存活時間
      idle-timeout: 60s
      # 是否啟用自動重試
      retry-on-failure: true
      # 重試次數(shù)
      max-retry-timeout: 3

BaseEntity實(shí)體類

package com.point.pack.entity.base;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.point.pack.constant.DateFormatConstant;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * Entity基類,提供了實(shí)體對象的基本屬性,如主鍵、創(chuàng)建時間和更新時間。
 * 這是一個抽象類,用于定義所有實(shí)體對象共有的屬性和行為。
 *
 * @param <ID> 實(shí)體的主鍵類型,繼承自Serializable,允許不同的實(shí)體使用不同的主鍵類型。
 * @return 無返回值
 * @date 2020.03.27
 */
@Data
public abstract class BaseEntity<ID extends Serializable> implements Serializable {

    @Id
    @ApiModelProperty(value = "主鍵")
    // MyBatis-Plus 注解,適用于 MySQL
    @TableId(type = IdType.ASSIGN_ID)
    // Spring Data Elasticsearch 注解,適用于 ES
    @Field(name = BaseIndexField.ID, type = FieldType.Keyword)
    @JsonSerialize(using = ToStringSerializer.class)
    private ID id;

    @ApiModelProperty(value = "創(chuàng)建時間")
    // MyBatis-Plus 注解,適用于 MySQL
    @TableField(fill = FieldFill.INSERT)
    // Spring Data Elasticsearch 注解,適用于 ES
    @Field(name = BaseIndexField.CREATE_TIME, type = FieldType.Date, format = DateFormat.date_time)
    @DateTimeFormat(pattern = DateFormatConstant.DEFAULT_DATETIME_FORMAT)
    @JsonFormat(pattern = DateFormatConstant.DEFAULT_DATETIME_FORMAT, timezone = "GMT+8")
    private LocalDateTime createTime;

    @ApiModelProperty(value = "更新時間")
    // MyBatis-Plus 注解,適用于 MySQL
    @TableField(fill = FieldFill.INSERT_UPDATE)
    // Spring Data Elasticsearch 注解,適用于 ES
    @Field(name = BaseIndexField.UPDATE_TIME, type = FieldType.Date, format = DateFormat.date_time)
    @DateTimeFormat(pattern = DateFormatConstant.DEFAULT_DATETIME_FORMAT)
    @JsonFormat(pattern = DateFormatConstant.DEFAULT_DATETIME_FORMAT, timezone = "GMT+8")
    private LocalDateTime updateTime;
}

FileInfo實(shí)體類

package com.point.pack.entity.common;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.point.pack.entity.base.BaseEntity;
import com.point.pack.entity.es.FileInfoIndexField;
import com.point.pack.enums.StorageType;
import com.point.pack.enums.YesNo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = FileInfoIndexField.INDEX_NAME)
@ApiModel(value = "FileInfo", description = "文件表")
public class FileInfo extends BaseEntity<Long> {
    private static final long serialVersionUID = 1L; // 序列化ID,用于版本控制

    @ApiModelProperty(value = "文件名稱")
    @Field(name = FileInfoIndexField.FILE_NAME, analyzer = "ik_max_word", searchAnalyzer = "ik_smart", type = FieldType.Text)
    private String fileName;

    @ApiModelProperty(value = "文件字節(jié)大小")
    @Field(name = FileInfoIndexField.FILE_SIZE, type = FieldType.Long)
    @JsonSerialize(using = ToStringSerializer.class)
    private Long fileSize;

    @ApiModelProperty(value = "文件類型,如image/jpeg")
    @Field(name = FileInfoIndexField.FILE_TYPE, type = FieldType.Keyword)
    private String fileType;

    @ApiModelProperty(value = "文件存儲路徑")
    @Field(name = FileInfoIndexField.FILE_PATH, type = FieldType.Keyword)
    private String fileSavePath;

    @ApiModelProperty(value = "文件訪問路徑")
    @Field(name = FileInfoIndexField.FILE_URL, type = FieldType.Keyword)
    private String fileAccessUrl;

    @ApiModelProperty(value = "存儲類型")
    @Field(name = FileInfoIndexField.STORAGE_TYPE, type = FieldType.Keyword)
    private StorageType storageType;

    @ApiModelProperty(value = "備注")
    @Field(name = FileInfoIndexField.REMARK, type = FieldType.Keyword)
    private String remark;

    @ApiModelProperty(value = "是否已刪除")
    @Field(name = FileInfoIndexField.IS_DELETE, type = FieldType.Keyword)
    private Integer isDelete = YesNo.NO.getValue();

}

FileInfoRepository類,這個非常重要
Spring Data Elasticsearch只有在應(yīng)用程序啟動時,帶有索引實(shí)體的ElasticsearchRepository發(fā)現(xiàn)該索引不存在時,才會自動寫入該索引的映射。
否則,不會對索引自動執(zhí)行任何操作。映射不會被重寫。因此,如果向?qū)嶓w添加某些屬性,即使實(shí)體上有注釋,也不會創(chuàng)建新映射。
您可以做的是,在添加屬性之后,在使用這個新屬性插入新數(shù)據(jù)之前,使用IndexOperations.putMapping()方法編寫更新的映射。

  • 重要提示:只能向索引添加新屬性/字段,不能刪除或更新現(xiàn)有屬性/字段;這是Elasticsearch的限制。
package com.point.user.service.repository;

import com.point.pack.entity.common.FileInfo;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface FileInfoRepository extends ElasticsearchRepository<FileInfo, Long> {

}

FileInfoController類

package com.point.user.controller.web;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.point.common.annotation.ResponseResult;
import com.point.pack.entity.common.FileInfo;
import com.point.pack.exception.CustomException;
import com.point.user.dto.IdDTO;
import com.point.user.dto.IdsDTO;
import com.point.user.dto.fileInfo.FileInfoAddDTO;
import com.point.user.dto.fileInfo.FileInfoQueryPageDTO;
import com.point.user.dto.fileInfo.FileInfoUpdateDTO;
import com.point.user.service.FileInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@ResponseResult
@RequestMapping("/web/fileInfo")
@Api(value = "文件模塊相關(guān)接口", tags = "文件模塊相關(guān)接口")
public class FileInfoController {

    @Resource
    private FileInfoService fileInfoService;

    @PostMapping("/queryPage")
    @ApiOperation(value = "分頁查詢文件", notes = "分頁查詢文件")
    public Page<FileInfo> queryPage(@Validated @RequestBody FileInfoQueryPageDTO condition) throws CustomException {
        return fileInfoService.queryPage(condition);
    }

    @PostMapping("/add")
    @ApiOperation(value = "添加文件", notes = "添加文件")
    public void add(@Validated @RequestBody FileInfoAddDTO condition) throws CustomException {
        fileInfoService.add(condition);
    }

    @PostMapping("/detail")
    @ApiOperation(value = "查詢文件詳情", notes = "查詢文件詳情")
    public FileInfo detail(@Validated @RequestBody IdDTO condition) throws CustomException {
        return fileInfoService.detail(condition);
    }

    @PostMapping("/update")
    @ApiOperation(value = "編輯文件", notes = "編輯文件")
    public void update(@Validated @RequestBody FileInfoUpdateDTO condition) throws CustomException {
        fileInfoService.update(condition);
    }

    @PostMapping("/deleteByIds")
    @ApiOperation(value = "批量刪除文件", notes = "批量刪除文件")
    public void deleteByIdList(@Validated @RequestBody IdsDTO condition) throws CustomException {
        fileInfoService.deleteByIdList(condition);
    }
}

FileInfoService類

package com.point.user.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.point.pack.entity.common.FileInfo;
import com.point.pack.exception.CustomException;
import com.point.user.dto.IdDTO;
import com.point.user.dto.IdsDTO;
import com.point.user.dto.fileInfo.FileInfoAddDTO;
import com.point.user.dto.fileInfo.FileInfoQueryPageDTO;
import com.point.user.dto.fileInfo.FileInfoUpdateDTO;

public interface FileInfoService {

    /**
     * 分頁查詢文件
     *
     * @param condition 分頁查詢條件,包含需要查詢的文件和分頁參數(shù)
     * @return 返回查詢到的文件分頁對象
     * @throws CustomException 當(dāng)查詢過程中發(fā)生錯誤時拋出自定義異常
     */
    Page<FileInfo> queryPage(FileInfoQueryPageDTO condition) throws CustomException;

    /**
     * 添加文件
     *
     * @param condition 添加文件的DTO,包含所需信息
     * @throws CustomException 添加過程中可能出現(xiàn)的自定義異常
     */
    Long add(FileInfoAddDTO condition) throws CustomException;

    /**
     * 查詢文件詳情
     *
     * @param condition 文件詳情查詢條件,包含用于篩選查詢結(jié)果的具體參數(shù)
     * @return 返回一個FileInfo對象,該對象包含查詢到的文件詳情
     */
    FileInfo detail(IdDTO condition) throws CustomException;

    /**
     * 編輯文件
     *
     * @param condition 更新文件的DTO,包含所需信息
     * @throws CustomException 更新過程中可能出現(xiàn)的自定義異常
     */
    void update(FileInfoUpdateDTO condition) throws CustomException;

    /**
     * 批量刪除文件
     *
     * @param condition 包含待刪除文件項(xiàng)ID列表的DTO對象,用于指定刪除條件
     * @throws CustomException 當(dāng)刪除操作失敗時拋出自定義異常,以便調(diào)用者處理錯誤情況
     */
    void deleteByIdList(IdsDTO condition) throws CustomException;
}

FileInfoServiceImpl

package com.point.user.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.point.pack.entity.common.FileInfo;
import com.point.pack.entity.es.FileInfoIndexField;
import com.point.pack.enums.YesNo;
import com.point.pack.exception.CustomException;
import com.point.user.dto.IdDTO;
import com.point.user.dto.IdsDTO;
import com.point.user.dto.fileInfo.FileInfoAddDTO;
import com.point.user.dto.fileInfo.FileInfoQueryPageDTO;
import com.point.user.dto.fileInfo.FileInfoUpdateDTO;
import com.point.user.service.FileInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

@Slf4j
@Service
public class FileInfoServiceImpl implements FileInfoService {

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 分頁查詢文件
     *
     * @param condition 分頁查詢條件,包含需要查詢的文件和分頁參數(shù)
     * @return 返回查詢到的文件分頁對象
     * @throws CustomException 當(dāng)查詢過程中發(fā)生錯誤時拋出自定義異常
     */
    @Override
    public Page<FileInfo> queryPage(FileInfoQueryPageDTO condition) throws CustomException {
        // 構(gòu)造查詢條件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withFilter(QueryBuilders.termQuery(FileInfoIndexField.IS_DELETE, YesNo.NO.getValue()));
        if (StringUtils.isNotBlank(condition.getFileName())) {
            queryBuilder.withFilter(QueryBuilders.matchQuery(FileInfoIndexField.FILE_NAME, condition.getFileName()));
        }
        if (StringUtils.isNotBlank(condition.getFileType())) {
            queryBuilder.withFilter(QueryBuilders.matchQuery(FileInfoIndexField.FILE_TYPE, condition.getFileType()));
        }
        if (condition.getStorageType() != null) {
            queryBuilder.withFilter(QueryBuilders.matchQuery(FileInfoIndexField.STORAGE_TYPE, condition.getStorageType()));
        }

        // 設(shè)置分頁
        Page<FileInfo> page = condition.getPage();
        int current = (int) (page.getCurrent() - 1); // 顯式轉(zhuǎn)換為int
        int size = (int) page.getSize(); // 顯式轉(zhuǎn)換為int
        Pageable pageable = PageRequest.of(current, size);
        queryBuilder.withPageable(pageable);
        // 設(shè)置排序
        queryBuilder.withSort(SortBuilders.fieldSort(FileInfoIndexField.ID).order(SortOrder.DESC));
        NativeSearchQuery query = queryBuilder.build();
        // 執(zhí)行查詢
        SearchHits<FileInfo> searchHits = elasticsearchRestTemplate.search(query, FileInfo.class);
        //設(shè)置總數(shù)
        page.setTotal(searchHits.getTotalHits());
        // 返回的行數(shù)
        List<SearchHit<FileInfo>> searchHitList = searchHits.getSearchHits();
        if (CollectionUtils.isNotEmpty(searchHitList)) {
            List<FileInfo> records = new ArrayList<>();
            for (SearchHit<FileInfo> searchHit : searchHits) {
                FileInfo fileInfo = searchHit.getContent();
                records.add(fileInfo);
            }
            page.setRecords(records);
        }

        return page;
    }

    /**
     * 添加文件
     *
     * @param condition 包含新文件的數(shù)據(jù)傳輸對象
     * @throws CustomException 如果添加過程中出現(xiàn)異常
     */
    @Override
    public Long add(FileInfoAddDTO condition) throws CustomException {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setId(Math.abs(UUID.randomUUID().getMostSignificantBits()));
        BeanUtils.copyProperties(condition, fileInfo);
        elasticsearchRestTemplate.save(fileInfo);
        return fileInfo.getId();
    }

    /**
     * 查詢文件詳情
     *
     * @param condition 文件詳情查詢條件,包含用于篩選查詢結(jié)果的具體參數(shù)
     * @return 返回一個FileInfo對象,該對象包含查詢到的文件詳情
     */
    @Override
    public FileInfo detail(IdDTO condition) throws CustomException {
        // 根據(jù)ID從Elasticsearch中查詢文件
        String id = String.valueOf(condition.getId());
        FileInfo fileInfo = elasticsearchRestTemplate.get(id, FileInfo.class);
        if (fileInfo == null) {
            throw new CustomException("文件不存在");
        }

        if (Objects.equals(fileInfo.getIsDelete(), YesNo.YES.getValue())) {
            throw new CustomException("文件已刪除");
        }

        return fileInfo;
    }

    /**
     * 編輯文件
     *
     * @param condition 更新文件的DTO,包含所需信息
     * @throws CustomException 更新過程中可能出現(xiàn)的自定義異常
     */
    @Override
    public void update(FileInfoUpdateDTO condition) throws CustomException {
        // 獲取要編輯的文件
        FileInfo updateFileInfo = elasticsearchRestTemplate.get(String.valueOf(condition.getId()), FileInfo.class);
        if (updateFileInfo == null) {
            throw new CustomException("文件不存在");
        }

        if (!Objects.equals(condition.getFileName(), updateFileInfo.getFileName()) || !Objects.equals(condition.getRemark(), updateFileInfo.getRemark())) {
            // 將更新后的信息復(fù)制到原字典類型對象并保存
            BeanUtils.copyProperties(condition, updateFileInfo);
            elasticsearchRestTemplate.save(updateFileInfo);
        }
    }

    /**
     * 批量刪除文件
     *
     * @param condition 包含待刪除文件項(xiàng)ID列表的DTO對象,用于指定刪除條件
     * @throws CustomException 當(dāng)刪除操作失敗時拋出自定義異常,以便調(diào)用者處理錯誤情況
     */
    @Override
    public void deleteByIdList(IdsDTO condition) throws CustomException {
        // 構(gòu)造查詢條件
        IdsQueryBuilder idsQueryBuilder = QueryBuilders.idsQuery();
        for (Long id : condition.getIds()) {
            idsQueryBuilder.addIds(String.valueOf(id));
        }

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(idsQueryBuilder)
                .build();

        // 執(zhí)行查詢
        SearchHits<FileInfo> searchHits = elasticsearchRestTemplate.search(query, FileInfo.class);
        List<FileInfo> fileInfos = new ArrayList<>();
        for (SearchHit<FileInfo> searchHit : searchHits) {
            fileInfos.add(searchHit.getContent());
        }

        if (CollectionUtils.isEmpty(fileInfos)) {
            return;
        }

        for (FileInfo fileInfo : fileInfos) {
            fileInfo.setIsDelete(YesNo.YES.getValue());
        }
        elasticsearchRestTemplate.save(fileInfos);
    }
}
Elasticsearch(ES)雖然是一款強(qiáng)大的分布式搜索和分析引擎,但它并不是為所有類型的數(shù)據(jù)存儲和管理場景設(shè)計(jì)的。以下是Elasticsearch不適合做數(shù)據(jù)存儲的一些原因:
1、事務(wù)支持不足

Elasticsearch 不支持傳統(tǒng)關(guān)系型數(shù)據(jù)庫中的 ACID 事務(wù)(Atomicity, Consistency, Isolation, Durability)。
它更適合最終一致性模型,而不是強(qiáng)一致性需求的場景。

2、高存儲成本

Elasticsearch 的倒排索引和分片機(jī)制會導(dǎo)致數(shù)據(jù)占用更多的存儲空間。
對于大規(guī)模寫入和存儲密集型的應(yīng)用,存儲成本會顯著增加。

3、復(fù)雜查詢性能問題

雖然 ES 在全文搜索和聚合查詢上表現(xiàn)優(yōu)異,但對于復(fù)雜的多表聯(lián)結(jié)(JOIN)或深度嵌套查詢,性能較差。
這些操作可能會導(dǎo)致資源消耗過高,影響整體系統(tǒng)穩(wěn)定性。

4、數(shù)據(jù)更新開銷大

Elasticsearch 是基于 LSM 樹(Log-Structured Merge Tree)的實(shí)現(xiàn),頻繁的更新操作會導(dǎo)致段合并(Segment Merging)壓力增大。
段合并會影響寫入性能,并可能引發(fā)磁盤 I/O 瓶頸。

5、缺乏嚴(yán)格的權(quán)限控制

默認(rèn)情況下,Elasticsearch 的安全性和權(quán)限控制功能較弱(需要額外購買 X-Pack 插件)。
對于敏感數(shù)據(jù)存儲,可能存在安全隱患。

6、不適用于結(jié)構(gòu)化數(shù)據(jù)

如果數(shù)據(jù)是高度結(jié)構(gòu)化的(如金融交易記錄、用戶信息等),關(guān)系型數(shù)據(jù)庫(如 MySQL、PostgreSQL)更為合適。
ES 更適合非結(jié)構(gòu)化或半結(jié)構(gòu)化數(shù)據(jù)(如日志、文檔、媒體元數(shù)據(jù))。

7、實(shí)時性限制

Elasticsearch 的近實(shí)時(Near Real-Time, NRT)特性意味著數(shù)據(jù)從寫入到可搜索之間存在延遲(通常為秒級)。
對于要求毫秒級實(shí)時性的場景,ES 可能無法滿足需求。

8、維護(hù)復(fù)雜度高

集群管理和調(diào)優(yōu)需要較高的技術(shù)門檻,包括分片分配、副本管理、內(nèi)存優(yōu)化等。
對于小型團(tuán)隊(duì)或資源有限的項(xiàng)目,維護(hù)成本較高。

總結(jié)

Elasticsearch 最適合用于全文搜索、日志分析、監(jiān)控數(shù)據(jù)處理等場景,而對于需要強(qiáng)一致性、復(fù)雜事務(wù)、嚴(yán)格權(quán)限控制或低成本存儲的場景,建議選擇其他更合適的數(shù)據(jù)庫解決方案(如關(guān)系型數(shù)據(jù)庫或?qū)iT的 NoSQL 數(shù)據(jù)庫)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容