本文详解如何在 go 的 mgo 驱动中正确处理 mongodb 文档间关系,推荐使用 objectid 引用 + 封装查询方法的模式,避免嵌套冗余、兼顾性能与可维护性。
在 MongoDB 这类文档型数据库中,“关系”并非通过外键强制约束,而是由应用层设计决定——是嵌入(Embedding)还是引用(Referencing)。mgo 作为底层驱动(非 ORM),不提供自动关联加载、懒加载或关系映射等高级功能,因此需开发者主动设计清晰、可读且高效的关系模型。
如问题中第二种方式所示,将 Friends 字段定义为 []bson.ObjectId 是符合 MongoDB 设计哲学的合理选择:
type User struct {
Id bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
Username string `json:"username" bson:"username"`
Email string `json:"email" bson:"email"`
Password string `json:"password" bson:"password"`
friends []bson.ObjectId `json:"-" bson:"friends"` // 小写字段:私有、不序列化到 JSON
}注意:我们将 friends 字段设为小写(friends 而非 Friends),使其成为未导出字段,既避免意外暴露给外部 API,又可通过自定义方法统一管控访问逻辑。
接着,为 User 类型添加一个语义清晰的 Friends() 方法,负责按 ID 批量查出完整用户数据:
func (u *User) Friends(session *mgo.Session) ([]User, error) {
if len(u.friends) == 0 {
return []User{}, nil
}
var users []User
err := session.DB("your_db").C("users").Find(bson.M{
"_id": bson.M{"$in": u.friends},
}).All(&users)
return users, err
}✅ 优势总结:
编写迁移脚本提取并去重 ID,避免数据丢失。mgo 不提供 ORM 式的关系管理,这恰是其轻量与可控的体现。真正的“关系意识”应体现在代码设计中:用私有 ID 字段表达引用意图,用公开方法封装关联查询逻辑。这种模式既尊重 MongoDB 的文档本质,又保持 Go 代码的明确性与可测试性——不是“让框架替你思考”,而是“用简洁的代码讲清楚你的数据故事”。