假设我有一个结构体:
type User struct {
Name string
Id int
Score int
}
如何将数据库行解析为结构体?假设有一个与数据库表具有相同模式的数据库表。我在下面添加了一个答案,但不确定它是否是最好的。
Go语言包的测试通常提供了做事情的方法。例如,从 database/sql/sql_test.go
中可以得到以下提示:
func TestQuery(t *testing.T) {
/* . . . */
rows, err := db.Query("SELECT|people|age,name|")
if err != nil {
t.Fatalf("Query: %v", err)
}
type row struct {
age int
name string
}
got := []row{}
for rows.Next() {
var r row
err = rows.Scan(&r.age, &r.name)
if err != nil {
t.Fatalf("Scan: %v", err)
}
got = append(got, r)
}
/* . . . */
}
func TestQueryRow(t *testing.T) {
/* . . . */
var name string
var age int
var birthday time.Time
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
/* . . . */
}
对于您的问题,将一行查询到结构中,翻译成类似以下内容:
var row struct {
age int
name string
}
err = db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&row.age, &row.name)
我知道这看起来与你的解决方案类似,但重要的是展示如何找到解决方案。
来自README:
sqlx 是一个库,它在go标准的
database/sql
库上提供了一组扩展。sqlx 版本的sql.DB
、sql.TX
、sql.Stmt
等保留了底层接口不变,因此它们的接口是标准接口的超集。这使得与使用 database/sql 的现有代码库集成相对容易。主要的附加概念包括:
- 将行解析到结构体(带嵌入式结构体支持)、映射和切片中
- 支持命名参数,包括预处理语句
Get
和Select
快速从查询到结构体/切片
README 还包括一个代码片段,演示如何将行扫描到结构体中:
type Place struct {
Country string
City sql.NullString
TelephoneCode int `db:"telcode"`
}
// Loop through rows using only one struct
place := Place{}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
err := rows.StructScan(&place)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%#v\n", place)
}
请注意,我们不必手动将每一列映射到结构体的字段上。sqlx有一些默认映射规则来将数据库列映射到结构体的字段上,并且可以使用标记指定数据库列(请注意上面Place
结构体中的TelephoneCode
字段)。您可以在文档中了解更多相关信息。
这里有一种方法 - 只需在Scan
函数中手动分配所有结构值。
func getUser(name string) (*User, error) {
var u User
// this calls sql.Open, etc.
db := getConnection()
// note the below syntax only works for postgres
err := db.QueryRow("SELECT * FROM users WHERE name = $1", name).Scan(&u.Id, &u.Name, &u.Score)
if err != nil {
return &User{}, err
} else {
return &u, nil
}
}
sql.NullString
并将结构体转换为JSON
,则生成的内容不够友好,需要使用VO
或类似的东西。 - deFreitasrows, err := connection.Query("SELECT `id`, `username`, `email` FROM `users`")
if err != nil {
panic(err.Error())
}
for rows.Next() {
var user User
if err := rows.Scan(&user.Id, &user.Username, &user.Email); err != nil {
log.Println(err.Error())
}
users = append(users, user)
}
这里有一个专门用于此目的的库:scany。
您可以像这样使用它:
type User struct {
Name string
Id int
Score int
}
// db is your *sql.DB instance
// ctx is your current context.Context instance
// Use sqlscan.Select to query multiple records.
var users []*User
sqlscan.Select(ctx, db, &users, `SELECT name, id, score FROM users`)
// Use sqlscan.Get to query exactly one record.
var user User
sqlscan.Get(ctx, db, &user, `SELECT name, id, score FROM users WHERE id=123`)
这个库的文档详尽,易于使用。
免责声明:我是该库的作者。
这里已经有一些很好的答案了,我想再补充一个我专门为解决在最常用的Golang库上执行简单查询时的复杂性问题而编写的SQL库;它叫做KSQL(不是Kafka的那个,K代表Keep It Stupid Simple,即保持简单愚蠢)。
使用它,你可以像这样将用户扫描到一个结构体中:
import (
"context"
"fmt"
"github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/adapters/kpgx"
)
type User struct {
Id int `ksql:"id"`
Name string `ksql:"name"`
Score int `ksql:"score"`
}
// Just a simple struct containing the table name and
// the name of the ID column or columns:
var UsersTable = ksql.NewTable("users", "id")
func main() {
ctx := context.Background()
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable TimeZone=UTC"
db, err := kpgx.New(ctx, dsn, ksql.Config{})
if err != nil {
panic("failed to connect database")
}
// Lets insert a user so the query below has something to return:
err = db.Insert(ctx, UsersTable, &User{
Name: "SomeUser",
Score: 42,
})
if err != nil {
panic(err)
}
var user User
err = db.QueryOne(ctx, &user, "FROM users WHERE name = $1", "SomeUser")
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", user) // {Id:1 Name:SomeUser Score:42}
}
使用: go-models-mysql sqlbuilder
val, err = m.ScanRowType(row, (*UserTb)(nil))
或者完整的代码
import (
"database/sql"
"fmt"
lib "github.com/eehsiao/go-models-lib"
mysql "github.com/eehsiao/go-models-mysql"
)
// MyUserDao : extend from mysql.Dao
type MyUserDao struct {
*mysql.Dao
}
// UserTb : sql table struct that to store into mysql
type UserTb struct {
Name sql.NullString `TbField:"Name"`
Id int `TbField:"Id"`
Score int `TbField:"Score"`
}
// GetFirstUser : this is a data logical function, you can write more logical in there
// sample data logical function to get the first user
func (m *MyUserDao) GetFirstUser() (user *User, err error) {
m.Select("Name", "Id", "Score").From("user").Limit(1)
fmt.Println("GetFirstUser", m.BuildSelectSQL().BuildedSQL())
var (
val interface{}
row *sql.Row
)
if row, err = m.GetRow(); err == nil {
if val, err = m.ScanRowType(row, (*UserTb)(nil)); err == nil {
u, _ := val.(*UserTb)
user = &User{
Name: lib.Iif(u.Name.Valid, u.Nae.String, "").(string),
Id: u.Id,
Score: u.Score,
}
}
}
row, val = nil, nil
return
}