
本文介绍如何在 go(使用 mgo 驱动)中绕过泛型缺失的限制,通过接口参数与指针传递机制实现类型安全、零复制的通用 dao,解决 crud 方法重复编写问题。
本文介绍如何在 go(使用 mgo 驱动)中绕过泛型缺失的限制,通过接口参数与指针传递机制实现类型安全、零复制的通用 dao,解决 crud 方法重复编写问题。
在 Go 1.18 之前(且即使在之后,若项目仍用旧版 mgo),由于语言不支持泛型,为每个实体(如 User、Cluster、Order)单独编写几乎完全相同的 DAO 方法极易导致大量模板式代码冗余。典型问题在于:mgo.Query.All() 要求传入指向具体切片类型的指针(如 &[]*User{}),而不能接受 interface{} 或 []interface{} —— 后者因类型系统限制无法隐式转换,直接引发 panic:“result argument must be a slice address”。
关键在于理解 All() 的签名与 Go 的反射语义:
func (q *Query) All(result interface{}) error它接收任意 interface{},但*内部会通过反射检查该值是否为可寻址的切片(即 `[]T)**。因此,你不能传users(值),也不能传[]interface{}(类型不匹配),而必须传&users—— 即一个指向已声明切片变量的指针,其底层类型为[]*T`。
✅ 正确实现如下(通用 DAO 方法):
// GenericFindAll 是通用查询方法,适用于任意实体切片
func (dao ClusterDao) GenericFindAll(result interface{}) error {
session, collection := dao.getCollection()
defer session.Close()
// 注意:此处 result 已是 *[]T 类型(如 *&[]*entity.User)
// mgo 内部将解引用并填充切片
return collection.Find(bson.M{}).All(result)
}✅ 调用方式(类型安全、无强制转换):
// 1. 声明目标切片变量(类型明确)
var users []*entity.User
// 2. 传入其地址 —— 这是关键!
if err := dao.GenericFindAll(&users); err != nil {
log.Fatal("查询用户失败:", err)
}
fmt.Printf("共查到 %d 个用户\n", len(users))⚠️ 常见错误与注意事项:
- ❌ 错误:
dao.GenericFindAll([]*entity.User{})
→ 传入的是切片字面量值,非可寻址变量,All()无法修改它。 - ❌ 错误:
dao.GenericFindAll(users)(users是[]*entity.User)
→ 传入的是值而非指针,All()收到的是[]*entity.User类型的 interface{},但期望*[]*entity.User。 - ✅ 正确前提:
result参数必须是*[]T形式,且T必须是能被 BSON 反序列化的结构体(字段带bsontag)。 - ? 安全增强:可在方法内增加类型校验(可选):
if reflect.TypeOf(result).Kind() != reflect.Ptr || reflect.TypeOf(result).Elem().Kind() != reflect.Slice { return errors.New("GenericFindAll: result must be a pointer to a slice") }
? 进阶建议:
- 将
GenericFindAll提升为嵌入式BaseDao结构体的方法,供所有实体 DAO 组合复用; - 结合
bson.M参数支持动态查询条件,实现Find(query bson.M, result interface{}) error; - 若升级至 mongo-go-driver,其
Cursor.All()同样遵循interface{}+ 指针模式,本方案无缝迁移。
通过精准把握 Go 的指针语义与 interface{} 的运行时行为,无需依赖代码生成或反射构造切片,即可实现简洁、高效、类型安全的通用数据访问层。


















