如何在Go中不使用ORM避免结构体与SQL列映射的样板代码
解决方案:基于泛型+反射实现自动列映射
不用ORM框架的话,结合Go的泛型和反射机制,配合结构体字段标签,就能自动完成SQL列与结构体字段的映射,避免手动编写样板代码。以下是具体实现方案:
1. 定义带映射标签的结构体
给结构体字段添加db标签,指定对应的SQL列名,只保留业务需要的字段:
import "time" type Order struct { OrderID int64 `db:"order_id"` Customer string `db:"customer_name"` OrderDate time.Time `db:"order_date"` } type Employee struct { EmpID int64 `db:"emp_id"` Name string `db:"emp_name"` Dept string `db:"department"` }
2. 实现泛型查询函数
参考Go官方Wiki的泛型思路,编写通用的泛型函数,利用反射自动匹配查询结果列和结构体字段:
import ( "database/sql" "reflect" "strings" ) // GetRows 通用泛型查询函数,自动映射SQL列到结构体 func GetRows[T any](db *sql.DB, query string, args ...any) ([]T, error) { rows, err := db.Query(query, args...) if err != nil { return nil, err } defer rows.Close() // 解析结构体字段与db标签的映射 t := reflect.TypeOf((*T)(nil)).Elem() fieldMap := make(map[string]int) for i := 0; i < t.NumField(); i++ { field := t.Field(i) colName := field.Tag.Get("db") if colName != "" { // 统一转小写,兼容SQL Server大小写不敏感特性 fieldMap[strings.ToLower(colName)] = i } } // 获取查询返回的列名列表 cols, err := rows.Columns() if err != nil { return nil, err } // 建立返回列与结构体字段的索引映射 colIndices := make([]int, len(cols)) for i, col := range cols { idx, ok := fieldMap[strings.ToLower(col)] if !ok { colIndices[i] = -1 // 标记为无对应字段的列 continue } colIndices[i] = idx } var result []T for rows.Next() { // 创建新的结构体实例 item := reflect.New(t).Elem() // 构建Scan目标切片:只映射有对应字段的列,其余用占位符忽略 scanTargets := make([]any, len(cols)) for i, idx := range colIndices { if idx == -1 { scanTargets[i] = new(sql.RawBytes) continue } scanTargets[i] = item.Field(idx).Addr().Interface() } if err := rows.Scan(scanTargets...); err != nil { return nil, err } result = append(result, item.Interface().(T)) } if err := rows.Err(); err != nil { return nil, err } return result, nil }
3. 业务函数调用示例
直接在业务查询函数中使用泛型函数,无需手动编写映射代码:
func GetCompletedOrders(db *sql.DB) ([]Order, error) { query := `SELECT order_id, customer_name, order_date FROM orders WHERE status = 'completed'` return GetRows[Order](db, query) } func GetDeptEmployees(db *sql.DB, dept string) ([]Employee, error) { query := `SELECT emp_id, emp_name, department FROM employees WHERE department = ?` return GetRows[Employee](db, query, dept) }
关键逻辑说明
- 反射解析标签:通过反射读取结构体字段的
db标签,建立SQL列名到结构体字段的映射关系 - 动态列匹配:查询前先获取返回列名,只将存在于结构体中的列映射到对应字段,自动忽略多余列
- 泛型复用:利用Go 1.18+的泛型特性,让同一个函数支持任意结构体类型的查询,避免重复编写类似逻辑
这个方案完全基于Go标准库,不需要引入第三方ORM框架,同时解决了遗留数据库多余列导致的Scan报错问题,也省去了手动编写映射代码的麻烦。
内容的提问来源于stack exchange,提问作者surfmuggle
相关产品推荐
相关产品推荐

