反射仅提取元数据(字段→列名、主键、非空等),不生成SQL语法;MySQL用LAST_INSERT_ID(),PostgreSQL必须用RETURNING id,需上层按dialect显式分支处理。

反射本身不生成数据库语法差异
别指望 reflect 能自动适配 MySQL 的 LAST_INSERT_ID() 和 PostgreSQL 的 RETURNING id——它连字段名和标签都得你手动定义,更别说方言语义。反射只做元数据提取:结构体字段 → 列名映射、非空约束标记、主键标识。真正的 SQL 语法分支必须由上层显式控制,比如传入 dialect string 参数,再用 switch dialect 分支拼接不同模板。
结构体标签必须显式声明方言相关属性
光靠 db:"user_name" 不够。要支持多库,得在 tag 里带语义,例如:
-
ID int `db:"id" sql:",pk,autoincr"`→ MySQL 用AUTO_INCREMENT,PostgreSQL 用SERIAL -
CreatedAt time.Time `db:"created_at" sql:"default:now(),notnull"`→ MySQL 用NOW(),PostgreSQL 用CURRENT_TIMESTAMP -
State string `db:"state" sql:"type:enum('active','inactive')"`→ SQLite 忽略,MySQL 保留,PostgreSQL 改用check (state in ('active','inactive'))
解析时用 field.Tag.Get("sql") 拿值,再按逗号切分后逐项匹配关键字,而不是硬写正则或空格分割。
INSERT/UPDATE 语句必须分离占位符与参数绑定逻辑
你不能用反射拼出 "INSERT INTO users (id, name) VALUES (?, ?)" 然后直接喂给 db.Exec()——这看似通用,但 PostgreSQL 需要 , ,SQLite 接受 ?,而 sqlx.Named() 要求 :name。正确做法是:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 反射阶段只产出两样东西:
[]string列名列表、[]interface{}值列表 - 具体 SQL 字符串交给方言构造器:传入列名列表后,由
mysqlDialect.InsertSQL(cols)或pgDialect.InsertSQL(cols)各自生成 - 参数一律走
db.Exec(sqlStr, args...),不拼字符串,不碰reflect.Value.Interface()直接塞值
否则,一旦字段是 *time.Time 且为 nil,v.Interface() 就 panic;或者自定义类型没实现 driver.Valuer,写进 DB 就类型错乱。
嵌套结构和匿名字段容易导致列名冲突
像 type User struct { Base BaseModel `db:",inline"` Name string `db:"name"` } 这种写法,如果 BaseModel 里也有 ID int `db:"id"`,反射遍历时会重复提取 id,最终生成的 INSERT 多一个字段。关键不是“展开”,而是“去重+前缀”。实操要点:
- 遇到
Anonymous == true且Tag.Get("db") == ",inline",递归处理其字段,但每个子字段名拼上前缀(如"base_id") - 用
map[string]bool记录已添加的列名,跳过重复项 - 禁止让两个不同字段映射到同一个 db 列名——反射不报错,但 SQL 执行时会字段数不匹配
最常被忽略的是:字段顺序不等于列顺序。MySQL 严格按 INSERT 中的列序绑定值,而反射遍历 NumField() 是定义顺序。别假设结构体字段顺序=建表顺序。

















