必须用 reflect.StructTag.Get() 解析 struct tag,不能直接当 map 用;field.Tag 是 reflect.StructTag 类型,调用 Get("db") 才能安全取值,返回空字符串表示不存在而非 panic,复合值如 db:"id,pk,auto" 需手动解析。

struct tag 必须用 reflect.StructTag.Get() 解析,不能直接当 map 用
字段的 field.Tag 是一个 reflect.StructTag 类型,不是字符串也不是 map。直接写 field.Tag["db"] 会编译失败;写 field.Tag.Get("db") 才能安全取值。更常见的是忽略空标签:即使字段没写 db:"...",tag.Get("db") 也返回空字符串,不是 nil —— 所以得用 strings.TrimSpace(tag.Get("db")) != "" 判定是否有效。
常见错误是把 tag 当成结构化数据直接拆解:db:"id,pk,auto" 这种复合值必须手动解析,reflect.StructTag 不提供自动分词或键值提取。建议用 strings.Split(tagVal, ",") 或正则匹配前缀(如 ^pk$、^default:(.+)$)。
生成 WHERE 条件时,要区分零值字段和显式忽略字段
反射拿到字段值后,不能只靠 v.IsZero() 判断是否参与条件生成——比如 int 字段值为 0 可能是合法业务值(如“状态=0”),也可能是未设置。真正该跳过的,是那些带 db:"-" 或 db:",ignore" 标签的字段。
-
db:"-"表示完全忽略,连字段名都不进 SQL -
db:",omitempty"类似 JSON,仅当值为零值时跳过(需配合逻辑判断) - 没标签的字段默认参与,但注意大小写转换规则是否统一(如
CreatedAt→created_at)
别依赖驼峰转下划线的“智能推导”,容易和真实列名不一致。有 db 标签就用它,没标签就报错或跳过,比自动转换更可控。
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
嵌套结构体字段不会自动展开,reflect.StructField.Anonymous 要手动处理
像 type User struct { BaseModel; Name string } 这种匿名嵌入,reflect.TypeOf(User{}).NumField() 会包含 BaseModel 字段,但它类型是 struct,field.Type.NumField() 才是其内部字段数。不递归处理的话,“ID int db:"id"”这种定义在 BaseModel 里就根本扫不到。
实操建议:
- 检查
field.Anonymous为 true 且field.Type.Kind() == reflect.Struct时,递归进入该类型遍历 - 跳过非导出字段(
field.PkgPath() != "")和指针类型(field.Type.Kind() == reflect.Ptr通常表示未初始化) - 缓存已处理过的
reflect.Type,避免重复反射开销——尤其在高频查询场景下,没缓存的 ORM 映射可能慢 3–5 倍
reflect.Value.Interface() 不能直接塞进 database/sql 参数列表
从结构体字段取值后调 v.Interface() 看似方便,但 database/sql 的 Query 和 Exec 要求每个参数都是 driver.Value 类型(如 *string、time.Time、sql.NullInt64)。直接传 interface{} 容易 panic,尤其是遇到自定义类型或 nil 指针时。
正确做法是按 v.Kind() 分支处理:
-
reflect.Ptr:先v.CanInterface() && !v.IsNil(),再v.Elem().Interface() -
reflect.Struct且是time.Time:直接v.Interface()(它实现了driver.Valuer) -
reflect.Struct且是sql.NullString等:必须调v.FieldByName("String").Interface()和v.FieldByName("Valid").Bool()组合判断 - 自定义类型(如
type UserID int64):若没实现driver.Valuer,反射拿到的是int64,但驱动不认识这个新类型名,会丢值
最隐蔽的坑是时间字段:time.Time 值本身没问题,但如果字段是 *time.Time 且为 nil,v.Interface() 返回 nil,database/sql 会把它当成 NULL,而你本意可能是跳过该条件——这时候就得结合 tag 规则和值状态一起决策。

















