反射仅负责字段与数据库列的元数据映射,不参与SQL生成和参数绑定;安全绑定必须由database/sql驱动通过预编译占位符(?或$1)完成,严禁拼接字符串。

反射只负责字段映射,不参与参数绑定
反射本身不生成可执行的 SQL 字符串,更不处理参数绑定——它只做一件事:把结构体字段名、类型、db 标签和数据库列名对应起来。真正安全的参数绑定必须交给 database/sql 驱动完成,靠 reflect.Value.Interface() 拼字符串进 SQL 是高危操作。
常见错误现象:panic: reflect: call of reflect.Value.Interface on zero Value 或 SQL 注入漏洞;根本原因是混淆了「元数据提取」和「SQL 执行」两个阶段。
- 字段值提取前必须校验:
v.Kind() == reflect.Ptr && v.IsNil(),否则对*string等 nil 指针调v.Interface()必崩 - 嵌套结构体需递归调用
v.Elem(),但每层都得先判断是否为指针且非 nil -
v.Tag.Get("db")返回的是字符串,要自己解析(如拆分user_name,omitempty),不能直接当列名或条件用
预编译语句必须显式分离 SQL 和参数
db.Query() 和 db.Prepare() 都支持位置参数(? 或 $1),但它们不接受反射拼出来的完整 SQL 字符串——你传进去的 SQL 里只能有占位符,所有值必须作为独立参数传入。
错误写法:db.Query("SELECT * FROM users WHERE name = '" + name + "'");正确路径是两段式:
立即学习“go语言免费学习笔记(深入)”;
- 反射阶段:生成
"SELECT * FROM users WHERE name = ? AND age > ?"这类纯占位符 SQL - 参数组装阶段:遍历结构体字段,把每个有效值 append 到
[]interface{}切片中 - 执行时:用
db.Query(sql, args...),注意...展开语法不可省
PostgreSQL 驱动认 ,MySQL 认 ?,混用会丢参数;sqlx.Named() 要求传 struct 或 map,不能和原生 db.Query() 混用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
IN 子句必须手动展开占位符
IN (?) 在几乎所有 Go 驱动里都不被支持——? 只能代入单个值,不能代入切片。想查 id IN (1,2,3),就得动态拼出 IN (?,?,?),再把切片元素转成 []interface{}。
实操要点:
- 用
v.Kind() == reflect.Slice判断字段是否为切片,再用v.Len()知道长度 - 占位符生成别硬写循环:
strings.Repeat("?,", n-1) + "?" - 切片转
[]interface{}必须手写循环:for i := 0; i - 空切片要特殊处理,否则
IN ()是语法错误
表名、列名、ORDER BY 不能用 ? 占位
? 和 $1 只能代入**值(value)**,数据库协议根本不允许它们出现在表名、列名、ORDER BY、GROUP BY 或 UNION 中。强行塞进去,不是报错就是被忽略成字面量。
所以反射可以帮你读 db:"user_name" 标签来拼列名,但这个拼接过程必须走白名单校验:
- 字段名来自结构体标签 → 提取后必须比对预设白名单:
slices.Contains(allowedCols, colName) - 排序字段来自 HTTP 查询参数 → 不能直接拼进 SQL,得先过滤:
if !slices.Contains([]string{"created_at", "name"}, sortField) { return err } - 表名绝对不能由用户输入决定,必须硬编码或从配置加载
最易被忽略的一点:反射拿到的字段名默认是 Go 的首字母大写形式(UserName),而标签里写的 db:"user_name" 是下划线风格——大小写不一致导致字段匹配失败,整个映射就断了,但程序不会 panic,只会静默漏字段。

















