Golang中Mysql的results.Scan(&pointerAddress)为何无法填充字段

Golang中Mysql的results.Scan(&pointerAddress)为何无法填充字段 我想使用结构体来填充查询结果,但它抛出了一个错误(sql:在 Scan 中期望 5 个目标参数,但得到 1 个)。我知道我必须手动按正确顺序传递字段。但是否有技巧可以做到这一点?

type ClientUsers struct {
	Id                        int    `json:"id"`
	FullName                  string `json:"fullName"`
	Username                  string `json:"username"`
	Email                     string `json:"email"`
	Password                  string `json:"password"`
}

for results.Next() {
	var clientUsers ClientUsers

	err := results.Scan(&clientUsers )

	if err != nil {
		fmt.Println("ERROR QUERY IN CLIENT USERS: ", err)
	}

	usersResponse = append(usersResponse, clientUsers )
}

更多关于Golang中Mysql的results.Scan(&pointerAddress)为何无法填充字段的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

嗨,非常感谢您的回答。

更多关于Golang中Mysql的results.Scan(&pointerAddress)为何无法填充字段的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果使用内置的 database/sql 包,你将无法实现该功能。Go 语言中存在一些 ORM(对象关系模型),例如 GORM - 旨在对开发者友好的 Golang 优秀 ORM 库。我从未使用过它,但它应该能提供类似你所需的功能。

你有几种选择。你可以仅为了扫描功能而使用ORM。例如,如果你想使用gorm,可以这样做:

type ClientUsers struct {
	Id                        int    `json:"id"`
	FullName                  string `json:"fullName"`
	Username                  string `json:"username"`
	Email                     string `json:"email"`
	Password                  string `json:"password"`
}
var clientUsers []ClientUsers
db.Raw("select * from client_users").Scan(&clientUsers)

我通常不喜欢将ORM用于简单查询之外的场景。在我的一个项目中,我使用GORM进行CRUD操作,以减少那些无需动脑的select/insert查询。然后对于所有更复杂/专门的查询,我使用原始SQL。

你也可以看看blockloop/scan,它更专注于扫描功能:

rows, err := db.Query("select * from client_users")

var clientUsers []ClientUsers
err := scan.Rows(&clientUsers, rows)

SQLBoiler也支持绑定:

GitHub - volatiletech/sqlboiler: Generate a Go ORM tailored to your database...

sqlx也是如此:

GitHub - jmoiron/sqlx: general purpose extensions to golang's database/sql

最后,你也可以相当容易地使用反射自己实现一些东西:

How to use rows.Scan of Go's database/sql

例如,你可以在其之上使用泛型构建一层抽象。

在Golang中,sql.Rows.Scan()需要接收与查询返回列数相匹配的参数数量。你的代码只传递了一个结构体指针,但查询返回了5列。你需要传递每个字段的指针。

以下是正确的实现方式:

type ClientUsers struct {
    Id        int    `json:"id"`
    FullName  string `json:"fullName"`
    Username  string `json:"username"`
    Email     string `json:"email"`
    Password  string `json:"password"`
}

for results.Next() {
    var clientUsers ClientUsers
    
    // 按查询列的顺序传递每个字段的指针
    err := results.Scan(
        &clientUsers.Id,
        &clientUsers.FullName,
        &clientUsers.Username,
        &clientUsers.Email,
        &clientUsers.Password,
    )
    
    if err != nil {
        fmt.Println("ERROR QUERY IN CLIENT USERS: ", err)
        continue
    }
    
    usersResponse = append(usersResponse, clientUsers)
}

如果你不想手动指定每个字段,可以使用反射自动映射,但需要注意性能影响:

import (
    "database/sql"
    "reflect"
    "strings"
)

func scanRowToStruct(rows *sql.Rows, dest interface{}) error {
    destValue := reflect.ValueOf(dest).Elem()
    destType := destValue.Type()
    
    columns, err := rows.Columns()
    if err != nil {
        return err
    }
    
    values := make([]interface{}, len(columns))
    valuePtrs := make([]interface{}, len(columns))
    
    for i := range values {
        valuePtrs[i] = &values[i]
    }
    
    if err := rows.Scan(valuePtrs...); err != nil {
        return err
    }
    
    for i, col := range columns {
        for j := 0; j < destType.NumField(); j++ {
            field := destType.Field(j)
            tag := field.Tag.Get("json")
            if strings.EqualFold(tag, col) || strings.EqualFold(field.Name, col) {
                fieldValue := destValue.Field(j)
                if values[i] != nil {
                    val := reflect.ValueOf(values[i])
                    if val.Type().ConvertibleTo(fieldValue.Type()) {
                        fieldValue.Set(val.Convert(fieldValue.Type()))
                    }
                }
                break
            }
        }
    }
    
    return nil
}

// 使用方式
for results.Next() {
    var clientUsers ClientUsers
    err := scanRowToStruct(results, &clientUsers)
    if err != nil {
        fmt.Println("ERROR: ", err)
        continue
    }
    usersResponse = append(usersResponse, clientUsers)
}

或者使用现有的库如sqlx来简化操作:

import "github.com/jmoiron/sqlx"

// 使用sqlx的StructScan
var clientUsers ClientUsers
err := sqlx.StructScan(results, &clientUsers)

// 或者直接使用sqlx的Select
err := db.Select(&usersResponse, "SELECT * FROM client_users")

使用sqlx是最常见的解决方案,它提供了结构体标签支持:

type ClientUsers struct {
    Id        int    `db:"id" json:"id"`
    FullName  string `db:"full_name" json:"fullName"`
    Username  string `db:"username" json:"username"`
    Email     string `db:"email" json:"email"`
    Password  string `db:"password" json:"password"`
}
回到顶部