借助Mongo-Java-Driver原子更新Event文档数组字段并计算可用票数
原子更新DocumentDB中含计算逻辑的数组元素
场景说明
我正在处理event集合,原始文档结构如下:
{ "event_name": "John Doe Concert", "event_date": "2022-01-01", "ticket_types": [ { "name": "Front seats", "total": 50, // oldTotal1 "available": 25 // oldAvailable1 }, { "name": "Back seats", "total": 100, // oldTotal2 "available": 50 // oldAvailable2 } ] }
REST API的PUT端点接收如下请求体:
{ "event_name": "Jane Doe Concert", "event_date": "2022-02-02", "ticket_types": [ { "name": "Front seats", "total": 100 // newTotal1 }, { "name": "Back seats", "total": 150 // newTotal2 } ] }
需要将文档原子更新为以下结构(已知newTotal[i] >= oldTotal[i]):
{ "event_name": "Jane Doe Concert", "event_date": "2022-02-02", "ticket_types": [ { "name": "Front seats", "total": 100, // newTotal1 "available": 75 // 计算逻辑:oldAvailable1 + newTotal1 - oldTotal1 }, { "name": "Back seats", "total": 150, // newTotal2 "available": 100 // 计算逻辑:oldAvailable2 + newTotal2 - oldTotal2 } ] }
核心需求
- 必须通过DocumentDB的
findAndUpdate完成原子更新,禁止先查询文档再用Java代码修改的非原子操作 - 运行环境为AWS Lambda,无法依赖外部锁机制,不使用事务API
- 使用DocumentDB 4.0,应用层为Java
解决方案:使用更新聚合管道
普通的$set操作无法引用文档当前字段进行计算,而更新聚合管道支持表达式运算,能满足原子计算更新的需求。以下是具体实现:
JavaScript示例(Mongo Shell/DocumentDB CLI)
db.events.findOneAndUpdate( { event_name: "John Doe Concert" }, // 查询条件 [ { $set: { event_name: "Jane Doe Concert", event_date: "2022-02-02", ticket_types: { $map: { input: "$ticket_types", as: "ticket", in: { $switch: { branches: [ { case: { $eq: ["$$ticket.name", "Front seats"] }, then: { name: "$$ticket.name", total: 100, // 对应请求体中的newTotal1 available: { $add: ["$$ticket.available", { $subtract: [100, "$$ticket.total"] }] } } }, { case: { $eq: ["$$ticket.name", "Back seats"] }, then: { name: "$$ticket.name", total: 150, // 对应请求体中的newTotal2 available: { $add: ["$$ticket.available", { $subtract: [150, "$$ticket.total"] }] } } } ], default: "$$ticket" // 未匹配的票型保持原有结构 } } } } } } ], { returnNewDocument: true } // 返回更新后的完整文档 )
Java代码示例
import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.Filters; import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.client.model.ReturnDocument; import org.bson.Document; import org.bson.conversions.Bson; import java.util.Arrays; // 1. 构建查询条件(此处以event_name为例,可根据实际主键调整) Bson query = Filters.eq("event_name", "John Doe Concert"); // 2. 构建更新聚合管道 Bson updatePipeline = Aggregates.set( "event_name", "Jane Doe Concert", "event_date", "2022-02-02", "ticket_types", new Document("$map", new Document("input", "$ticket_types") .append("as", "ticket") .append("in", new Document("$switch", new Document("branches", Arrays.asList( // 处理Front seats票型 new Document("case", new Document("$eq", Arrays.asList("$$ticket.name", "Front seats"))) .append("then", new Document("name", "$$ticket.name") .append("total", 100) // 替换为请求体中的newTotal1 .append("available", new Document("$add", Arrays.asList( "$$ticket.available", new Document("$subtract", Arrays.asList(100, "$$ticket.total")) ))), // 处理Back seats票型 new Document("case", new Document("$eq", Arrays.asList("$$ticket.name", "Back seats"))) .append("then", new Document("name", "$$ticket.name") .append("total", 150) // 替换为请求体中的newTotal2 .append("available", new Document("$add", Arrays.asList( "$$ticket.available", new Document("$subtract", Arrays.asList(150, "$$ticket.total")) ))) )) .append("default", "$$ticket"))) // 其他票型保持不变 ); // 3. 设置更新选项:返回更新后的文档 FindOneAndUpdateOptions options = new FindOneAndUpdateOptions() .returnDocument(ReturnDocument.AFTER); // 4. 执行原子更新并获取结果 Document updatedDocument = collection.findOneAndUpdate(query, updatePipeline, options);
逻辑说明
$map遍历ticket_types数组,对每个元素逐一处理$switch根据票型名称匹配对应的更新规则,确保每个票型的total和available按需求更新$add和$subtract组合实现available = 旧available值 + (新total值 - 旧total值)的计算逻辑- 整个操作是文档级原子操作,完全在DocumentDB端完成,符合Lambda环境的无锁、无事务需求
内容的提问来源于stack exchange,提问作者Lorenzo Valente
相关产品推荐
相关产品推荐

