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

MongoTemplate实现Upsert时如何结合@CreatedDate/@LastModifiedDate?

解决Spring Data MongoDB Upsert中@CreatedDate/@LastModifiedDate不生效的问题

你遇到的核心问题是:Spring Data MongoDB的审计注解(@CreatedDate、@LastModifiedDate)不会自动作用于findAndReplace这类原子操作——因为findAndReplace是直接向MongoDB发送原子命令,绕过了Spring Data的实体生命周期回调机制(而save/insert会触发AuditingEntityListener来自动填充审计字段)。

下面提供两种解决方案,优先推荐第一种(兼顾原子性和需求逻辑):

方案1:利用MongoDB原生操作符实现原子Upsert(推荐)

直接使用mongoTemplate.upsert()结合Update对象,通过MongoDB的$set和$setOnInsert操作符,在原子操作中实现你的时间字段逻辑:

  • 无论文档是否存在,都用$set更新modifiedAt为当前时间
  • 仅当插入新文档时,用$setOnInsert设置createdAt

代码示例:

// 1. 构建查询条件
Query query = new Query().addCriteria(Criteria.where("yourField").is(yourValue)); // 替换为你的查询条件

// 2. 构建Update操作,实现Upsert的时间逻辑
ZonedDateTime now = ZonedDateTime.now();
Update update = new Update()
    .set("modifiedAt", now)
    .setOnInsert("createdAt", now);

// 3. 执行原子Upsert操作
UpdateResult result = mongoTemplate.upsert(query, update, MyPojo.class);

// (可选)如果后续需要使用实体对象,手动填充生成的ID和时间字段
if (result.getUpsertedId() != null) {
    MyPojo newObj = new MyPojo();
    // 填充其他业务字段...
    newObj.setId(result.getUpsertedId().asString().getValue());
    newObj.setCreatedAt(now);
    newObj.setModifiedAt(now);
    // 使用newObj做后续操作
}

这个方案的优势:

  • 完全原子性,避免并发场景下的竞态问题
  • 直接利用MongoDB原生能力,不需要依赖Spring Data的审计回调
  • 完美匹配你的需求:新文档同时设置两个时间,已存在文档仅更新modifiedAt

方案2:手动触发审计逻辑(不推荐,存在并发风险)

如果坚持要使用findAndReplace和审计注解,你可以手动调用AuditingHandler来填充审计字段,但需要注意查询和更新之间不是原子操作,可能出现并发问题:

// 1. 先查询文档是否存在(非原子步骤)
Query query = new Query().addCriteria(...);
boolean exists = mongoTemplate.exists(query, MyPojo.class);

// 2. 获取Spring容器中的AuditingHandler,手动触发审计
AuditingHandler auditingHandler = applicationContext.getBean(AuditingHandler.class);
MyPojo obj = new MyPojo();
// 填充obj的其他业务字段...

if (exists) {
    // 文档已存在,仅更新modifiedAt
    auditingHandler.markModified(obj);
} else {
    // 文档不存在,设置createdAt和modifiedAt
    auditingHandler.markCreated(obj);
}

// 3. 执行findAndReplace
MyPojo result = mongoTemplate.findAndReplace(query, obj, new FindAndReplaceOptions().upsert());

为什么不推荐这个方案?

因为“查询是否存在”和“执行findAndReplace”是两个独立操作,在高并发场景下,可能出现查询时文档不存在,但执行upsert前其他线程已经插入了文档的情况,导致createdAt被错误覆盖。

补充说明:为什么save/insert能生效?

save和insert方法会触发Spring Data MongoDB的实体生命周期回调,AuditingEntityListener会在@PrePersist(插入前)和@PreUpdate(更新前)阶段自动填充@CreatedDate和@LastModifiedDate字段,而findAndReplace是直接操作数据库的命令,不会经过这些回调环节,所以注解不生效。

内容的提问来源于stack exchange,提问作者Jade Dezo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 09:02:51