在小编学习elasticsearch搜索引擎进行数据同步时发现一个问题,如果文档的唯一标识符字段_id不是自己控制则用默认的会出现我们插入数据会出现重复,这显然是数据冗余对我们项目业务造成不同的问题,因此我研究整理了问题的解决方案,代码笔记简单比较好上手

Elasticsearch 中的 _id 字段

在 Elasticsearch 中,每个文档都有一个 _id 字段,它是文档的唯一标识符。Elasticsearch 使用该字段来执行文档的 CRUD(创建、读取、更新和删除)操作。

  • 自动生成 _id:如果你在索引文档时没有显式设置 _id,Elasticsearch 会自动生成一个唯一的 _id。

  • 显式设置 _id:你也可以在索引文档时显式提供 _id,Elasticsearch 会使用这个自定义的 _id 进行存储,并且在后续操作中引用这个 _id。

注意:如果字段名不是 id 则使用注解 @id 也不会显式设置成文档的 _id(目前不清楚问题核心有知道的大佬欢迎评论区留言)

方案一:使用实体类结合字段名为 id 存储数据

这种方案简单易用,但不够规范,容易导致潜在的代码不一致性,尤其是在数据迁移或项目扩展时。

实体类:
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
/**
 * @author:XXX
 * @date:X-X-X X:X:X
 * 功能:XXX-实体类
 **/
@Data
@Document(indexName = "news ")   // 标注一个 Java 类,以指定该类对应的 Elasticsearch 索引,必须全小写
public class News {
    @Id   
    private String id; // 使用注解@Id,可以让字段名是id就会作为文档的_id唯一标识符,如果不是字段名不是id则需要额外设置
    private String title;
}
存储数据:
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
/**
 * @author:XXX
 * @date:XXXX-XX-XX XX:XX
 * 功能:XX
 **/
@Service
@RequiredArgsConstructor
public class ElasticsearchService {
    private final ElasticsearchOperations operations;
 /**
     * 根据文档数据判断 添加 或 更新 操作。
     * @param news XX-实体类
     */
    public void upsertDocument(News news) {
        // 存储数据,根据文档中的_id判断,存在则更新否则添加
        operations.save(news);
    }
}

方案二:使用实体类序列化成文档传输对象(DTO)

这种方式推荐使用,符合标准规范。将实体类与传输对象(DTO)解耦,适用于多层架构中的数据传输。

实体类:

import lombok.Data;
import org.springframework.data.annotation.Id;
/**
 * @author:XXX
 * @date:X-X-X X:X:X
 * 功能:XXX-实体类
 **/
@Data
public class News {
    @Id  // 可以不设置,为了规范实体类也需要标注主键
    private String systemId; // 我这里故意设置字段名为systemId
    private String title;
}
文档传输对象
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import java.util.Date;
/**
 * @author:XXX
 * @date:XXX-XXX-XX XX:XX
 * 功能:XXX-传输对象
 **/
@Data
@Document(indexName = "newsvo")  须全小写
public class NewsVO {
    @Id
    private String id;
    private String title;
}
对象映射转换:使用 MapStruct 进行对象之间的转换,确保数据的一致性和准确性。
import org.example.news.module.vo.NewsVO;
import org.example.news.module.entity.News;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * @author:XXX
 * @date:XXX-XX-XX XX:XX
 * 功能:对象之间的映射转换
 **/
@Mapper
public interface NewsConverter {
     // 接口实例
    NewsConverter INSTANCE = Mappers.getMapper(NewsConverter.class);
    @Mappings({
        
        @Mapping(source = "systemId", target = "id"), // 重点: 字段转换
        @Mapping(source = "title", target = "title")
    })
    /**
     * 正向转换,转换为适用于文档的数据传输对象
     * @return 返回NewsVO对象
     */
    NewsVO toNewsVO(News news);


    /**
     * 反向转换,可以继承某个正向转换的方法进行反向赋值,用于取值
     * @return 返回News对象
     */
    @InheritInverseConfiguration(name = "toNewsVO")
    News toNews(NewsVO newsVO);
}

存储数据:

 
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
/**
 * @author:XXX
 * @date:XXXX-XX-XX XX:XX
 * 功能:XX
 **/
@Service
@RequiredArgsConstructor
public class ElasticsearchService {
    private final ElasticsearchOperations operations;
    /**
     * 根据文档数据判断 添加 或 更新 操作。
     * @param newsVO 新闻数据传输对象
     */
    public void upsertDocument(NewsVO newsVO) {
        // 存储数据,根据文档中的_id判断,存在则更新否则添加
        operations.save(newsVO);
    }
}
   
优点:
  • 解耦实体类与传输对象,符合多层架构的设计原则。

  • 通过 MapStruct 等工具简化对象转换逻辑,提高代码的可维护性和扩展性。

  • 支持字段命名转换,灵活处理不同的字段名映射问题。

数据查看:

可以使用 Elasticsearch 提供的 API 来查询存储的数据结构:

GET http://127.0.0.1:9200/newsvo/_search

总结:

  • 方案一:适用于小规模项目或快速原型开发,但由于不规范,后期维护较为困难。

  • 方案二:推荐用于正式项目,符合数据分层和解耦设计原则,能够更好地支持可维护性和扩展性。

总结: 有问题欢迎评论区或者私聊我看到会解决,一只不会摸鱼的博主只发干货\(^▽^)/

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐