Golang如何在不使用Scan方法时获取数据库查询结果
核心结论
Go标准库database/sql 没有提供绕过Scan方法直接读取rows内部结果数据的公开API,所有结果集读取最终都要走Scan完成。你完全可以在封装层基于Scan实现通用结果映射,不需要向外暴露rows对象。
通用封装实现思路
整个封装逻辑完全在数据访问层内部处理,核心分三步:
- 拿到查询返回的
rows对象后,先调用Columns()获取所有返回列的名称,需要做精确类型适配的话还可以调用ColumnTypes()拿到每列的数据库类型、对应Go类型等元信息 - 遍历每一行时,动态构造和列数等长的
[]interface{}切片,每个元素都是对应值的指针,直接传给Scan接收任意类型的返回值 Scan执行完成后,既可以把列名和对应值组装成map[string]interface{}返回,也可以通过反射把值绑定到上层传入的结构体对应字段上。
参考实现代码
通用查询返回键值对结果
func QueryRows(db *sql.DB, sqlStr string, args ...interface{}) ([]map[string]interface{}, error) { rows, err := db.Query(sqlStr, args...) if err != nil { return nil, err } defer rows.Close() columns, err := rows.Columns() if err != nil { return nil, err } var res []map[string]interface{} for rows.Next() { // 构造Scan接收的指针切片 scanArgs := make([]interface{}, len(columns)) for i := range scanArgs { var val interface{} scanArgs[i] = &val } if err := rows.Scan(scanArgs...); err != nil { return nil, err } // 组装行数据 row := make(map[string]interface{}, len(columns)) for idx, colName := range columns { rawVal := *(scanArgs[idx].(*interface{})) // 多数数据库驱动返回字符串/文本类型时默认是[]byte,按需转成string方便上层使用 if byteVal, ok := rawVal.([]byte); ok { row[colName] = string(byteVal) } else { row[colName] = rawVal } } res = append(res, row) } // 检查rows遍历过程中抛出的错误 if err = rows.Err(); err != nil { return nil, err } return res, nil }
如果需要支持直接绑定到指定结构体,只需要在拿到列名后,通过反射解析结构体的字段、db/column这类tag,建立列名到结构体字段内存地址的映射,把这些地址组成切片传给Scan即可,不需要额外转map,性能更高。市面上主流的数据库扩展库核心绑定逻辑都是基于这个思路实现的,没有用到非公开API。
内容的提问来源于stack exchange,提问作者discodowney
相关产品推荐
相关产品推荐

