You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 08:50:57