不能靠 reflect 直接拼 sql,必须走预编译参数绑定;传指针并调 elem() 是硬性前提,需校验 nil 指针;field.tag 是字符串,须用 get("db") 提取后解析;嵌套结构体需递归扁平化;字段匹配和赋值须双重校验,且严禁将 interface() 直接拼入 sql。

不能靠 reflect 直接拼 SQL,它只负责把结构体字段和数据库列名、类型、约束对应起来——映射归映射,SQL 构建必须走预编译参数绑定。
传指针、调 Elem() 是硬性前提
反射操作结构体字段前,必须确保拿到的是可寻址、可设置的值。传 *User 进来后,reflect.ValueOf(entity) 得到的是指针的 Value,不调 .Elem() 就没法遍历字段,NumField() 会返回 0。
- 如果传入
nil指针,.Elem()会 panic,需提前校验:if v.Kind() == reflect.Ptr && v.IsNil() { return errNilPointer } - 传非指针(如
User{})也能过反射,但后续写入字段时CanSet()全为 false,赋值失败 -
reflect.TypeOf(entity).Name()只在传结构体本身时有效;若传指针,得先.Elem()再取Name()
field.Tag.Get("db") 不是 map,别当字典用
field.Tag 是字符串,不是 map[string]string。写 field.Tag["db"] 会编译报错或 panic。必须用 tag := field.Tag.Get("db") 提取原始标签值,再手动解析。
- 空字符串
""和显式忽略标记"-"都要跳过,不能只判== "" - 复合标签如
db:"id,pk,autoincr"得用strings.Split(tag, ",")拆解,再逐项判断语义 - 带前缀的 inline 嵌套字段(如
Extra Info `db:",inline"`)需递归展开,并拼接字段名前缀(如extra_created_at)
嵌套匿名字段必须手动扁平化
reflect.Value.Field(i) 只返回一级字段,不会自动展开 BaseModel 这类嵌入结构体里的 ID、CreatedAt。ORM 要的是扁平字段列表,必须自己递归处理。
- 检查
field.Anonymous == true且field.Type.Kind() == reflect.Struct - 跳过非导出字段:
field.PkgPath != ""表示未导出,不能映射 - 用栈或队列维护待处理字段,避免无限递归(比如循环嵌入)
- 漏掉这步,基类字段在 INSERT 里直接消失,调试时很难定位
字段匹配和赋值必须双重校验
从数据库查出一行数据往结构体里塞,不能只靠字段名大小写转换匹配。列名来自 DB(如 created_at),Go 字段是 CreatedAt,靠规则转换不可靠。
- 优先用
field.Tag.Get("db")值做精确匹配,没匹配上就报错,不静默跳过 - 赋值前必须
field.CanSet(),私有字段永远过不了这一关 - 设值不用
field.SetString()这种语法(那是方法调用),要用field.SetString(val)或field.Set(reflect.ValueOf(val)) -
time.Time、sql.NullString等类型,零值判断不能用== nil或== "",得用field.IsZero()(Go 1.13+)
真正容易被忽略的点是:反射只管“字段怎么映”,不管“值是否安全”。哪怕标签解析全对、字段匹配无误、赋值也成功,只要把 reflect.Value.Interface() 直接塞进 SQL 字符串,就等于把用户输入裸奔上数据库——注入风险、NULL 写入、时间格式错乱,三颗雷都在那儿等着。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











