如何升级旧版MongoDB Java驱动代码至4.7.0并实现UPSERT?
问题背景
维护一个7年历史的遗留应用,升级至MongoDB Java驱动4.7.0时,大量同类方法出现编译失败,原代码及报错如下:
原代码
@Override public void saveOrUpdatePrinter(Document printer) { printer.put(PRINTER_COLUMNS.updateDate,new Date()); MongoCollection<Document> collection = mongoTemplate.getCollection("PRINTERS"); printer.remove("_id"); Document query = new Document().append(PRINTER_COLUMNS.internal_id, printer.get(PRINTER_COLUMNS.internal_id)); WriteResult result = collection.update(query, printer, true, false); logger.debug("saveOrUpdatePrinter updeded records: " + result.getN()); }
报错信息
The method update(Document, Document, boolean, boolean) is undefined for the type MongoCollection
以下针对问题逐一解答:
问题解答
1. 旧版update方法被移除的原因
MongoDB Java驱动从3.x版本开始重构CRUD API,核心目的是让API语义更贴合MongoDB原生命令,同时支持更灵活的配置(如读写关注、事务等)。旧版update(Document, Document, boolean, boolean)属于早期简化API,参数对应查询条件、更新文档、是否UPSERT、是否批量更新,这种设计语义模糊,且无法扩展更多配置项。新版驱动改用updateOne、updateMany、replaceOne等命名明确的方法,替代原模糊的update,让代码逻辑更清晰,也能更好适配驱动新特性。
2. printer.remove("_id")的作用
这段代码的核心作用是移除文档中的_id字段,原因有两点:
- 原逻辑是UPSERT操作(第三个参数
true表示无匹配则插入),若保留_id,插入时会强制使用该值,但原逻辑以internal_id为唯一标识,不需要自定义_id,移除后MongoDB会自动生成默认ObjectId作为主键。 - 若为更新操作,
_id是文档主键不可被修改,保留会导致更新报错,因此必须移除。
3. 新版驱动实现UPSERT操作的方式
原代码核心是基于internal_id的UPSERT:匹配则更新,无匹配则插入。新版驱动有两种主流实现方式:
方式一:replaceOne整体替换文档(与原逻辑最接近)
@Override public void saveOrUpdatePrinter(Document printer) { printer.put(PRINTER_COLUMNS.updateDate, new Date()); MongoCollection<Document> collection = mongoTemplate.getCollection("PRINTERS"); printer.remove("_id"); Document query = new Document().append(PRINTER_COLUMNS.internal_id, printer.get(PRINTER_COLUMNS.internal_id)); // 开启UPSERT选项 ReplaceOptions options = new ReplaceOptions().upsert(true); UpdateResult result = collection.replaceOne(query, printer, options); logger.debug("saveOrUpdatePrinter updated/inserted records: " + result.getModifiedCount() + ", inserted id: " + result.getUpsertedId()); }
方式二:updateOne更新指定字段(适合无需整体替换的场景)
如果只需更新部分字段,而非整体替换文档,可使用Updates工具类构建更新操作:
@Override public void saveOrUpdatePrinter(Document printer) { Date updateDate = new Date(); MongoCollection<Document> collection = mongoTemplate.getCollection("PRINTERS"); String internalId = printer.getString(PRINTER_COLUMNS.internal_id); Document query = new Document().append(PRINTER_COLUMNS.internal_id, internalId); // 构建多字段更新操作 UpdateDefinition update = Updates.combine( Updates.set(PRINTER_COLUMNS.updateDate, updateDate), Updates.set("printerName", printer.get("printerName")), Updates.set("status", printer.get("status")) // 按需添加其他需更新的字段 ); UpdateOptions options = new UpdateOptions().upsert(true); UpdateResult result = collection.updateOne(query, update, options); logger.debug("saveOrUpdatePrinter updated records: " + result.getModifiedCount() + ", inserted id: " + result.getUpsertedId()); }
4. 新版驱动中单文档更新的正确方式
新版驱动针对单文档更新提供了两种明确方法:
updateOne:更新匹配到的第一个文档,适合仅更新部分字段的场景,需配合UpdateDefinition(如Updates.set、Updates.inc等)定义具体更新操作。replaceOne:用新文档替换匹配到的第一个文档,适合整体替换的场景,注意替换时新文档无需包含_id(除非需自定义主键)。
两种方法都可通过UpdateOptions配置额外参数(如UPSERT、写关注),返回的UpdateResult可获取更新数量、插入ID等信息,替代旧版的WriteResult。
内容的提问来源于stack exchange,提问作者JavaSheriff

