不能直接用 reflect 拼接安全的 DML SQL,因反射不处理 SQL 注入防护、字段名转义、空值语义、时间格式化、struct tag 解析等关键问题;必须结合标签解析、类型判断、时区转换与专用库(如 sqlx、squirrel)才能生成安全可移植的 SQL。

为什么不能直接用 reflect 拼接安全的 DML SQL?
因为反射本身不处理 SQL 注入、字段名转义、空值语义、时间格式化、结构体标签解析等关键问题。直接用 reflect.Value.Field(i).Interface() 拼字符串,等于把 fmt.Sprintf("INSERT INTO t (a) VALUES ('%v')", v) 当成正经方案——只要字段含单引号或 SQL 关键字,就立刻报错或被注入。
必须配合 struct tag 和类型判断才敢生成 INSERT
Go 的 reflect 无法自动知道哪个字段该进 INSERT、哪个该忽略(比如 ID 自增)、哪个要转成 NULL 或带引号。得靠 struct 标签显式声明:
type User struct {
ID int64 `db:"id,primary_key,auto_increment"`
Name string `db:"name,not_null"`
Age *int `db:"age"`
Time time.Time `db:"created_at"`
}
实操建议:
- 用
reflect.StructTag.Get("db")解析字段映射,拆出列名和修饰符(如primary_key、ignore) - 对
nil指针、零值、未导出字段跳过写入(!f.CanInterface()) -
time.Time必须调用.Format("2006-01-02 15:04:05"),不能直接fmt.Sprint - 字符串、字节切片需用
strconv.Quote包裹,避免单引号破坏 SQL 结构
UPDATE 语句必须区分 set 和 where 条件
反射能读字段值,但无法自动判断哪些是更新字段、哪些是主键条件。常见错误是把 ID 同时塞进 SET 和 WHERE,或者漏掉 WHERE 导致全表覆盖。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 扫描所有字段,按
dbtag 中是否含primary_key分两组:setFields / whereFields - 只对非
primary_key且非ignore字段生成SET col = ? - where 字段必须非空,否则 panic 或返回 error(绝不能静默生成无 where 的 UPDATE)
- 参数顺序要严格按 set + where 拼接顺序排列,否则
database/sql绑定错位
别硬写,用 sqlx.In 或 squirrel 替代手拼
自己用反射生成完整 SQL 字符串,很快会陷入 quote 转义、MySQL/PostgreSQL 列名括号差异(`col` vs "col")、批量插入占位符数量对齐等问题。更稳妥的做法是:
- 用
sqlx.StructScan反向做映射,再借助sqlx.In处理 IN 查询和参数绑定 - 用
squirrel构建 AST 式 SQL:它不碰反射,但允许你封装一个BuildInsert(u interface{}) (string, []interface{})函数,内部用反射提取字段+值,外部仍走 squirrel 的PlaceholderFormat和RunWith - 如果必须纯反射输出 SQL 字符串(比如日志调试),务必加开关控制:仅开发环境开启,生产强制走预编译语句
最常被忽略的是:反射拿到的 time.Time 默认 zone 是本地时区,而数据库通常设 UTC;不显式 .In(time.UTC) 就算 SQL 拼对了,时间也会偏移。

















