You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data MongoDB:如何防止@CreatedBy与@CreatedDate字段被更新?

解决Spring Boot MongoDB审计字段(CreatedBy/CreatedDate)不可篡改且返回正确值的问题

我理解你遇到的问题:继承AuditingDocument的实体在通过REST API更新时,创建审计字段(creator/created)可能被篡改,而且自定义MongoTemplate后数据库数据正确,但save方法返回的对象中这些字段为null。下面给出一套完整的解决方案,既能保护审计字段不被更新,又能让save返回包含正确字段值的对象。

核心思路

  1. 拦截更新操作,过滤受保护字段:通过MongoDB事件监听器,在执行更新前移除creator、created等不允许修改的字段,确保这些字段不会被请求中的值覆盖。
  2. 返回更新后的最新文档:修改MongoTemplate的save方法,使用findAndModify执行更新并返回数据库中最新的文档,解决返回对象字段为null的问题。

具体实现

1. 创建审计字段保护的事件监听器

这个监听器会在所有MongoDB更新操作执行前触发,过滤掉我们不允许修改的审计字段:

import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeUpdateEvent;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

@Component
public class AuditingFieldProtectionListener extends AbstractMongoEventListener<Object> {

    // 定义受保护的字段:创建审计字段+乐观锁版本号
    private static final String[] PROTECTED_FIELDS = {"creator", "created", "version"};

    @Override
    public void onBeforeUpdate(BeforeUpdateEvent<Object> event) {
        Update filteredUpdate = new Update();

        // 遍历原更新请求中的所有字段,仅保留非受保护的字段
        event.getUpdate().getUpdateObject().forEach((key, value) -> {
            boolean isProtected = false;
            for (String field : PROTECTED_FIELDS) {
                if (field.equals(key)) {
                    isProtected = true;
                    break;
                }
            }
            if (!isProtected) {
                filteredUpdate.set(key, value);
            }
        });

        // 将过滤后的更新替换原更新,确保受保护字段不会被修改
        event.setUpdate(filteredUpdate);
    }
}

2. 自定义MongoTemplate,返回更新后的最新文档

默认的MongoTemplate.save方法在更新时会返回传入的实体对象(可能缺失审计字段),我们重写该方法,使用findAndModify来执行更新并返回数据库中最新的文档:

import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.util.Assert;

public class CustomMongoTemplate extends MongoTemplate {

    public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory) {
        super(mongoDbFactory);
    }

    public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
        super(mongoDbFactory, mongoConverter);
    }

    @Override
    public <T> T save(T objectToSave) {
        return save(objectToSave, getCollectionName(objectToSave.getClass()));
    }

    @Override
    public <T> T save(T objectToSave, String collectionName) {
        Assert.notNull(objectToSave, "Object to save must not be null!");

        Object id = getId(objectToSave);
        if (id == null) {
            // 插入操作:直接调用父类方法,审计字段会自动填充
            return super.save(objectToSave, collectionName);
        } else {
            // 更新操作:使用findAndModify返回最新的文档
            Query query = Query.query(Criteria.where("_id").is(id));
            Update update = Update.fromDocument(getConverter().write(objectToSave, new Document()));
            
            // 配置选项:返回更新后的文档
            FindAndModifyOptions options = FindAndModifyOptions.options().returnNew(true);
            
            return findAndModify(query, update, options, (Class<T>) objectToSave.getClass(), collectionName);
        }
    }
}

3. 注册自定义MongoTemplate

在配置类中替换默认的MongoTemplate Bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;

@Configuration
public class MongoConfig {

    @Bean
    public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
        return new CustomMongoTemplate(mongoDbFactory, mongoConverter);
    }
}

方案验证

  1. 创建文档:首次调用save时,审计字段(creator/created/version/modifier/modified)会被正确填充,返回的对象包含所有字段值。
  2. 更新文档:
    • 即使请求中传入creator或created的新值,这些字段也不会被更新到数据库。
    • save方法返回的对象会从数据库加载最新数据,包含正确的creator和created字段值。
  3. 通用性:所有继承AuditingDocument的@Document实体都会自动应用这个保护逻辑,无需逐个修改。

为什么你的原方案会出现返回值为null的问题

你之前的CustomMongoTemplate.doUpdate方法虽然保证了数据库中的字段不被覆盖,但save方法默认返回的是传入的实体对象(而该对象中creator/created可能为null),不是数据库中更新后的文档。使用findAndModify并配置returnNew(true)可以直接获取更新后的最新文档,完美解决这个问题。

内容的提问来源于stack exchange,提问作者1Z10

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:24:05