首页 > Java > java教程 > 正文

Spring Boot MongoDB 审计中的重复键异常处理及日期字段管理

碧海醫心
发布: 2025-11-28 14:27:02
原创
854人浏览过

spring boot mongodb 审计中的重复键异常处理及日期字段管理

本文旨在深入探讨Spring Boot与MongoDB集成时,使用Spring Data Auditing功能可能遇到的`DuplicateKeyException`问题,并提供基于`Persistable`接口的解决方案。同时,文章将详细分析在解决重复键异常后,`@CreatedDate`字段可能无法正确保存的后续问题,并给出正确的实践方法,以确保审计字段的完整性和准确性。

Spring Data MongoDB 审计功能概述

Spring Data MongoDB提供了强大的审计功能,允许开发者自动记录实体(Document)的创建时间、最后修改时间、创建者和最后修改者。这对于追踪数据变更历史、满足合规性要求以及调试都非常有价值。核心注解包括:

  • @CreatedDate: 标记实体创建时的日期时间。
  • @LastModifiedDate: 标记实体最后一次修改时的日期时间。
  • @CreatedBy: 标记实体创建者的用户ID或名称。
  • @LastModifiedBy: 标记实体最后修改者的用户ID或名称。
  • @Version: 用于乐观锁,防止并发更新冲突。

要启用审计功能,通常需要在配置类上添加@EnableMongoAuditing注解,并提供一个AuditorAware实现来获取当前操作的用户信息(如果需要@CreatedBy和@LastModifiedBy)。

示例配置 (AuditingConfig.java):

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

import java.util.Optional;

@Configuration
@EnableMongoAuditing
public class AuditingConfig {

    @Bean
    public AuditorAware<String> myAuditorProvider() {
        return new AuditorAwareImpl();
    }
}
登录后复制

审计元数据基类 (AuditMetadata.java):

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Version;

import java.time.LocalDateTime;

// @Setter @Getter 是Lombok注解,此处省略
public class AuditMetadata {

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

    @Version
    private Long version;

    // ... getters and setters
}
登录后复制

审计员实现 (AuditorAwareImpl.java):

import org.springframework.data.domain.AuditorAware;

import java.util.Optional;

public class AuditorAwareImpl implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of("Admin"); // 实际应用中应从安全上下文中获取
    }
}
登录后复制

DuplicateKeyException:问题分析与根源

在使用Spring Data MongoDB的审计功能时,有时会遇到org.springframework.dao.DuplicateKeyException,尤其是在尝试更新现有实体时。错误信息通常类似于:

org.springframework.dao.DuplicateKeyException: Write operation error on server user.domain.com:27017. Write error: WriteError{code=11000, message='E11000 duplicate key error collection: springboot.category index: *id* dup key: { _id: "21" }', details={}}.
登录后复制

这个错误表明MongoDB尝试插入一个已经存在_id值的文档。其根本原因在于Spring Data在执行save()操作时,未能正确判断当前实体是“新”的(需要执行insert操作)还是“已存在”的(需要执行update操作)。当Spring Data误认为一个带有ID的现有实体是新实体时,就会尝试进行插入操作,从而触发DuplicateKeyException。

这通常发生在实体类没有实现org.springframework.data.domain.Persistable接口,或者Persistable接口的isNew()方法实现不正确的情况下。

解决方案:实现 Persistable 接口

为了解决DuplicateKeyException,我们需要明确告诉Spring Data一个实体是否是新创建的。这通过实现org.springframework.data.domain.Persistable接口来完成。

Persistable接口包含两个方法:

  • getId(): 返回实体的ID。
  • isNew(): 返回一个布尔值,指示实体是否是新创建的。如果为true,Spring Data将执行插入操作;如果为false,则执行更新操作。

1. 更新 AuditMetadata 基类

为了更好地管理实体的“新旧”状态,可以在AuditMetadata中引入一个persisted标志。

腾讯交互翻译
腾讯交互翻译

腾讯AI Lab发布的一款AI辅助翻译产品

腾讯交互翻译 183
查看详情 腾讯交互翻译
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Version;

import java.time.LocalDateTime;

// @Setter @Getter 是Lombok注解,此处省略
public class AuditMetadata {

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

    @Version
    private Long version;

    // 用于Persistable接口判断实体是否已持久化
    protected boolean persisted;

    // ... getters and setters
}
登录后复制

2. 实现 Persistable 接口的实体类

让你的MongoDB实体类实现Persistable<ID类型>接口,并实现其方法。

示例实体类 (CategoryMongo.java):

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.domain.Persistable;
import org.springframework.lang.Nullable;

// @Getter @Setter @NoArgsConstructor 是Lombok注解,此处省略
@Document(collection = "category")
public class CategoryMongo extends AuditMetadata implements Persistable<String> {

    @Id
    @JsonProperty("category_id")
    private String category_id;

    @JsonProperty("id_colletion")
    private String emberId; // 注意:此字段在示例中用于返回category_id,可能引起混淆,建议统一ID字段

    @JsonProperty("category_name")
    private String name;

    @JsonProperty("category_active")
    private ProductEnum active = ProductEnum.ativo;

    @JsonProperty("category_slug")
    private String slug;

    // 实现Persistable接口的方法
    @Override
    @Nullable
    public String getId() {
        return category_id;
    }

    @Override
    public boolean isNew() {
        // 根据persisted标志判断实体是否为新
        // 默认情况下,新创建的实体persisted为false,isNew()返回true
        // 当从数据库加载实体时,或者手动标记为已持久化时,persisted为true,isNew()返回false
        return !persisted;
    }

    // ... getters and setters (包括 emberId 的 getter,如果需要)
    public String getEmberId() {
        return category_id; // 示例中将emberId指向category_id
    }
}
登录后复制

通过这种方式,当Spring Data调用isNew()方法时,它会根据persisted字段的值来判断是否执行插入或更新。

CreatedDate 字段丢失问题及正确处理

在解决了DuplicateKeyException之后,可能会出现一个新的问题:@CreatedDate字段在更新操作后消失,只剩下@LastModifiedDate。这通常是由于对persisted标志的错误管理导致的。

问题分析

@CreatedDate注解的字段只会在实体第一次被持久化(即被认为是“新”实体时)时被Spring Data填充。如果isNew()方法返回false,即使实体是新创建的,@CreatedDate也不会被设置。

在原始的解决方案中,save方法如下:

// 原始解决方案中的save方法
CategoryMongo catm = new CategoryMongo();
catm.setName(category.getName());
catm.setSlug(category.getSlug());
catm.setActive(category.getActive());
catm.setCategory_id(category.getCategory_id().toString());
catm.setPersisted(true); // 问题所在:为新创建的实体过早地设置persisted为true
categoryRepositoryMongo.save(catm);
登录后复制

这段代码中,catm.setPersisted(true);在categoryRepositoryMongo.save(catm);之前被调用,而catm是一个刚刚通过new CategoryMongo()创建的对象。这意味着:

  1. new CategoryMongo()创建了一个新的实体对象。
  2. catm.setPersisted(true);将persisted标志设置为true。
  3. 当categoryRepositoryMongo.save(catm)被调用时,Spring Data会检查catm.isNew()。由于isNew()返回!persisted,此时它将返回false。
  4. Spring Data误认为这是一个已存在的实体,因此不会设置@CreatedDate。如果category_id已经存在,则会尝试更新;如果category_id不存在,由于isNew()为false,它可能不会执行插入操作,或者行为变得不确定(取决于Spring Data版本和具体实现)。

正确的 save 方法策略

为了确保@CreatedDate和@LastModifiedDate都能正确工作,关键在于正确管理isNew()的返回值,使其准确反映实体的真实状态:

  • 创建新实体时: isNew()必须返回true。
  • 更新现有实体时: isNew()必须返回false。

1. 创建新实体

当创建一个全新的实体并首次保存时,不应手动设置persisted为true。AuditMetadata中persisted字段的默认值(false)是正确的。

// 创建新实体并保存
public CategoryMongo createCategory(CategoryDto category) {
    CategoryMongo newCatm = new CategoryMongo();
    newCatm.setName(category.getName());
    newCatm.setSlug(category.getSlug());
    newCatm.setActive(category.getActive());
    // 如果ID由应用程序生成,可以在此处设置
    // newCatm.setCategory_id(UUID.randomUUID().toString());
    // 如果ID由数据库生成,则无需设置

    // 关键:不要在这里设置 newCatm.setPersisted(true);
    // 此时 newCatm.isNew() 默认为 true,@CreatedDate 会
登录后复制

以上就是Spring Boot MongoDB 审计中的重复键异常处理及日期字段管理的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号