
本文介绍在 go 语言中无需泛型即可复用 mongodb 查询函数的简洁实践:通过 `interface{}` 参数接收任意结构体指针,由底层驱动自动完成类型传递与填充,避免反射、类型断言或冗余代码。
在 Go 中实现跨结构体复用数据库查询逻辑,关键在于理解 interface{} 的行为本质——它不仅是一个“空接口”,更是类型信息的载体。当我们将一个具体类型的指针(如 *User、*Post 或 *Product)传入接受 interface{} 参数的函数时,Go 运行时会完整保留该值的动态类型和底层数据地址。这意味着,只要目标库(如 mgo 或 mongo-go-driver)的 One() 方法签名支持 interface{}(事实如此),你完全无需手动反射或类型转换。
以下是推荐的重构写法:
func findEntry(db, table string, entry interface{}, finder bson.M) error {
c := mongoSession.DB(db).C(table)
return c.Find(finder).One(entry)
}调用方式直观自然:
// 查询 User
var user User
err := findEntry("mydb", "users", &user, bson.M{"email": "alice@example.com"})
// 查询 Post
var post Post
err := findEntry("mydb", "posts", &post, bson.M{"slug": "hello-world"})
// 查询 Product(结构体字段名与 BSON 字段匹配即可)
var product Product
err := findEntry("mydb", "products", &product, bson.M{"sku": "PROD-001"})✅ 为什么这样可行?
c.Find(...).One(entry) 内部直接对 entry 进行反射操作(但这是驱动层封装好的细节),它通过 entry 的 reflect.Value 获取指针指向的可寻址结构体,并逐字段映射 BSON 键值。你传入 &user,驱动就写入 User;传入 &post,就写入 Post——全程零额外开销。
⚠️ 注意事项:
- entry 必须是指针(如 &user),否则 One() 无法修改原始变量(Go 是值传递);
- finder 使用 bson.M(即 map[string]interface{})而非 *bson.M,指针无意义且易引发 panic;
- 结构体字段需通过 bson tag(如 `bson:"name"`)显式声明映射关系,确保与数据库字段一致;
- 若传入非指针(如 user 而非 &user),函数将编译通过但运行时报错:panic: reflect.Set: value of type User is not assignable to type *User。
总结:Go 的 interface{} 在此处是轻量而强大的抽象机制。它不是“类型擦除”,而是“类型委托”——把类型责任交给调用方和底层库协同完成。这种模式广泛应用于标准库(如 json.Unmarshal、fmt.Printf)及主流驱动中,是符合 Go 惯用法的最佳实践,既安全、高效,又保持代码极简。









