Go语言如何通过反射获取结构体字段tag对应的数据库列名
实现方案
仅通过标准库的reflect配合少量unsafe代码就可以实现你要的能力,不需要引入第三方依赖。
核心实现逻辑
通过传入结构体字段的指针,计算字段相对结构体起始地址的偏移量,匹配到对应的结构体字段后读取db标签值。这种方式没有歧义,实现简单,还可以通过缓存优化性能。
代码实现
单结构体方法版本
适合仅需要给特定结构体增加该能力的场景:
package main import ( "reflect" "unsafe" ) // 你的结构体定义 type CusipTableRow struct { Id int64 `db:"id"` Cusip string `db:"cusip"` Symbol string `db:"symbol"` Active int8 `db:"active"` Added_Time int32 `db:"added_timestamp"` Description string `db:"description"` Exchange string `db:"exchange"` AssetType string `db:"asset_type"` } // GetColumnName 传入结构体字段指针,返回对应的db标签值 func (r *CusipTableRow) GetColumnName(fieldPtr interface{}) string { // 校验入参是指针 fieldVal := reflect.ValueOf(fieldPtr) if fieldVal.Kind() != reflect.Ptr { return "" } // 计算字段相对结构体的偏移量 structAddr := uintptr(unsafe.Pointer(r)) fieldAddr := fieldVal.Pointer() offset := fieldAddr - structAddr // 遍历字段匹配偏移量 structType := reflect.TypeOf(r).Elem() for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) if field.Offset == offset { return field.Tag.Get("db") } } return "" }
调用示例
func main() { var row CusipTableRow println(row.GetColumnName(&row.Id)) // 输出 id println(row.GetColumnName(&row.Added_Time)) // 输出 added_timestamp }
通用优化版本
如果需要给多个结构体复用该能力,可以加全局缓存避免每次遍历字段,同时适配所有带db标签的结构体:
package main import ( "reflect" "sync" "unsafe" ) var ( // 缓存结构体类型 -> 字段偏移量 -> db标签值 tagCache = make(map[reflect.Type]map[uintptr]string) // 缓存读写锁,保证多协程安全 cacheMu sync.RWMutex ) // GetDBTag 通用方法,传入结构体指针和字段指针,返回对应的db标签值 func GetDBTag(structPtr interface{}, fieldPtr interface{}) string { // 校验入参:第一个参数必须是结构体指针 structVal := reflect.ValueOf(structPtr) if structVal.Kind() != reflect.Ptr || structVal.Elem().Kind() != reflect.Struct { return "" } structType := structVal.Elem().Type() // 优先读缓存 cacheMu.RLock() cache, ok := tagCache[structType] cacheMu.RUnlock() // 无缓存则构建 if !ok { cache = make(map[uintptr]string) for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) cache[field.Offset] = field.Tag.Get("db") } cacheMu.Lock() tagCache[structType] = cache cacheMu.Unlock() } // 校验字段指针入参 fieldVal := reflect.ValueOf(fieldPtr) if fieldVal.Kind() != reflect.Ptr { return "" } // 计算偏移量查缓存 structAddr := structVal.Pointer() fieldAddr := fieldVal.Pointer() offset := fieldAddr - structAddr return cache[offset] }
通用版本调用示例
func main() { var row CusipTableRow println(GetDBTag(&row, &row.Id)) // 输出 id println(GetDBTag(&row, &row.AssetType)) // 输出 asset_type }
注意事项
- 无法实现
row.GetColumnName(row.Id)这种传值的调用方式:值参数仅传递数值本身,不携带所属结构体、对应字段的关联信息,程序无法区分同类型的不同字段。 - 传入的字段指针必须是对应结构体实例的成员指针,不能传入其他结构体的字段指针或无关指针,否则会返回空值或错误结果。
- 该方案的unsafe操作符合Go官方的使用规范,结构体字段偏移量是编译期确定的,跨平台编译不会出现兼容问题。
内容的提问来源于stack exchange,提问作者ControlAltDelete
相关产品推荐
相关产品推荐

