本文详解如何使用 mgo 在 go 中正确建模父子文档关系——既保持结构清晰(parent 中嵌入 child 类型),又实现在数据库中分离存储(parent 引用 child id,child 独立存于 children 集合),避免字段丢失或冗余嵌套。
在使用 mgo 操作 MongoDB 时,一个常见误区是试图通过 bson:"-" 标签“屏蔽”子文档字段来实现引用式存储,但这会导致整个字段(包括 _id)在序列化时被完全忽略,无法满足“仅存 ID、独立建模”的需求。正确的做法是区分数据模型(domain model)与持久化模型(storage model),并善用 BSON 标签控制序列化行为。
为 Child 定义两个类型:一个用于业务逻辑(含完整字段),一个专用于 Parent 中的引用(仅含 ID):
type Child struct {
Id bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
C string `json:"c" bson:"c"`
}
// ChildRef 仅用于引用,不包含业务字段
type ChildRef struct {
Id bson.ObjectId `json:"_id" bson:"_id"`
}
type Parent struct {
Id bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
A string `json:"a" bson:"a"`
B ChildRef `json:"b" bson:"b"` // 注意:此处是 ChildRef,非 Child
}插入时分别处理:
child := Child{
Id: bson.NewObjectId(),
C: "panino",
}
err := session.DB("mydb").C("children").Insert(child) // 存入 children 集合
parent := Parent{
Id: bson.NewObjectId(),
A: "Just a string",
B: ChildRef{Id: child.Id}, // 仅传递 ID
}
err = session.DB("mydb").C("parents").Insert(parent) // 存入 parents 集合,B 字段仅含 {_id: "..."}这样既保证了代码中 Parent.B 语义清晰(表示一个 Child 的引用),又确保数据库中 parents.b 仅为 ObjectId,而 children 集合完整保存 Child 全量数据。
若坚持复用同一 Child 类型,可将非 ID 字段标记为 omitempty,但需谨慎:
type Child struct {
Id bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
C string `json:"c,omitempty" bson:"c,omitempty"` // 仅当 C == "" 时省略
}⚠️ 注意:这仅在 C 为空值时跳过该字段;若 C 有值(如 "panino"),它仍会被嵌入到 Par

通过这种设计,你既能享受 Go 类型系统的表达力,又能精准控制 MongoDB 中的数据落地形态。