Go语言实现将任意结构体字段指针填充切片用于动态查询Scan
嘿,刚学Go就尝试写通用的数据库查询函数,这思路很赞!我来帮你解决获取结构体字段指针的问题,顺便优化下代码里的小坑~
核心问题分析
- 你当前复用了传入的
entity变量,每次扫描都会覆盖它的值,最后allRows里的所有元素都会是最后一次扫描的结果——因为它们都是同一个对象的引用。 - 获取字段指针的方式不对,需要通过反射创建新的结构体实例,再逐个获取字段的地址。
修改后的完整代码
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 }
关键细节解释
- 强制传入结构体指针:这样我们可以通过
reflect.New(entityType)创建新的结构体实例,避免值类型的拷贝问题,也符合Go中传递指针修改数据的习惯。 - 每次循环创建新实例:
reflect.New(entityType)会生成一个指向新结构体的指针,这样每次扫描的结果都会存在独立的对象里,不会互相覆盖。 - 获取字段指针:
val.Field(i).Addr().Interface()是核心——val.Field(i)获取结构体的第i个字段,Addr()获取该字段的指针,最后Interface()转换成interface{}类型,供rows.Scan使用。 - 完善错误处理:把原来的
panic改成返回错误,调用者可以更优雅地处理;同时添加了defer确保数据库连接和结果集被正确关闭。 - 返回结果切片:修改函数返回
[]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
相关产品推荐
相关产品推荐

