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

Go语言实现将任意结构体字段指针填充切片用于动态查询Scan

嘿,刚学Go就尝试写通用的数据库查询函数,这思路很赞!我来帮你解决获取结构体字段指针的问题,顺便优化下代码里的小坑~

核心问题分析

  1. 你当前复用了传入的entity变量,每次扫描都会覆盖它的值,最后allRows里的所有元素都会是最后一次扫描的结果——因为它们都是同一个对象的引用。
  2. 获取字段指针的方式不对,需要通过反射创建新的结构体实例,再逐个获取字段的地址。

修改后的完整代码

import (
	"database/sql"
	"fmt"
	"log"
	"reflect"
)

// 注意:传入的entity必须是结构体指针,比如&User{}
func QuerySelect(entity interface{}, sqlQuery string) ([]interface{}, error) {
	// 先验证传入的是结构体指针
	entityType := reflect.TypeOf(entity)
	if entityType.Kind() != reflect.Ptr || entityType.Elem().Kind() != reflect.Struct {
		return nil, fmt.Errorf("entity must be a pointer to a struct")
	}
	entityType = entityType.Elem() // 获取结构体的类型

	// 数据库连接部分,记得用defer关闭连接
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
	db, err := sql.Open(driver, psqlInfo)
	if err != nil {
		return nil, fmt.Errorf("failed to open database: %w", err)
	}
	defer db.Close() // 确保函数退出时关闭数据库连接

	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("failed to ping database: %w", err)
	}

	rows, err := db.Query(sqlQuery)
	if err != nil {
		return nil, fmt.Errorf("failed to execute query: %w", err)
	}
	defer rows.Close() // 关闭结果集

	var allRows []interface{}
	countColumns := entityType.NumField()

	for rows.Next() {
		// 每次循环创建一个新的结构体实例(指针类型)
		newEntity := reflect.New(entityType).Interface()
		// 获取结构体的可设置Value(解引用指针)
		val := reflect.ValueOf(newEntity).Elem()

		columnsPointers := make([]interface{}, countColumns)
		for i := 0; i < countColumns; i++ {
			// 获取第i个字段的指针,转换成interface{}
			columnsPointers[i] = val.Field(i).Addr().Interface()
		}

		if err := rows.Scan(columnsPointers...); err != nil {
			return nil, fmt.Errorf("failed to scan row: %w", err)
		}

		// 将新实例添加到结果切片
		allRows = append(allRows, newEntity)
	}

	// 检查遍历过程中是否出现错误
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating rows: %w", err)
	}

	return allRows, nil
}

关键细节解释

  1. 强制传入结构体指针:这样我们可以通过reflect.New(entityType)创建新的结构体实例,避免值类型的拷贝问题,也符合Go中传递指针修改数据的习惯。
  2. 每次循环创建新实例:reflect.New(entityType)会生成一个指向新结构体的指针,这样每次扫描的结果都会存在独立的对象里,不会互相覆盖。
  3. 获取字段指针:val.Field(i).Addr().Interface()是核心——val.Field(i)获取结构体的第i个字段,Addr()获取该字段的指针,最后Interface()转换成interface{}类型,供rows.Scan使用。
  4. 完善错误处理:把原来的panic改成返回错误,调用者可以更优雅地处理;同时添加了defer确保数据库连接和结果集被正确关闭。
  5. 返回结果切片:修改函数返回[]interface{},调用者可以通过类型断言把每个元素转换成对应的结构体类型,比如user := result[0].(*User)。

使用示例

假设你有一个User结构体:

type User struct {
	ID   int
	Name string
	Age  int
}

调用QuerySelect的方式:

results, err := QuerySelect(&User{}, "SELECT id, name, age FROM users")
if err != nil {
	log.Fatal(err)
}

for _, item := range results {
	user := item.(*User)
	fmt.Printf("User: %+v\n", user)
}

这样就能实现你想要的“任意结构体动态查询”的功能啦~

内容的提问来源于stack exchange,提问作者Vinicius Gabriel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:58:22