如何在Go中将数据库行转换为结构?

Kev*_*rke 63 sql go

假设我有一个结构:

type User struct {
    Name  string
    Id    int
    Score int
}
Run Code Online (Sandbox Code Playgroud)

和具有相同模式的数据库表.将数据库行解析为结构的最简单方法是什么?我在下面添加了一个答案,但我不确定它是最好的答案.

pet*_*rSO 82

包裹测试经常提供关于做事方式的线索.例如,来自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)
    /* . . . */
}
Run Code Online (Sandbox Code Playgroud)

对于您的问题,将行查询到结构中,将转换为以下内容:

var row struct {
    age  int
    name string
}
err = db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&row.age, &row.name)
Run Code Online (Sandbox Code Playgroud)

我知道这看起来与您的解决方案类似,但重要的是展示如何找到解决方案.

  • 如果简单的方法是手动将列绑定到struct字段我想知道什么是困难的方法 (41认同)
  • 不幸的是,这不是很方便,特别是在更大的结构的情况下 - 手动绑定到struct属性是完全失败...使用jmoiron/sqlx或其他一些库更有效... (9认同)
  • 与Brian一样的WTF时刻.原来它是纯粹用于测试sql /数据库的假驱动程序(https://golang.org/src/database/sql/fakedb_test.go)我真的希望我可以用它来代替新代码! (6认同)
  • 这里的管道语法到底是什么?"SELECT | people"?在godocs中我没有看到任何提及. (5认同)

cke*_*ney 40

我推荐github.com/jmoiron/sqlx.

来自README:

sqlx是一个库,它在go的标准database/sql库上提供了一组扩展 .的SQLX版本sql.DB,sql.TX, sql.Stmt,等.所有都保持底层接口不变,因此它们的接口是标准接口的超集.这使得使用数据库/ sql与sqlx集成现有代码库相对轻松.

主要的附加概念是:

  • Marshal行成结构(具有嵌入式结构支持),映射和切片
  • 命名参数支持包括预准备语句
  • GetSelect快速从查询到结构/切片

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)
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们不必手动将每列映射到结构的字段.sqlx有一些结构字段到数据库列的默认映射,以及能够使用标记指定数据库列(请注意上面结构的TelephoneCode字段Place).您可以在文档中阅读更多相关信息.


Kev*_*rke 33

这是一种方法 - 只需在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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如何为null值工作? (5认同)

小智 7

rows, 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)
}
Run Code Online (Sandbox Code Playgroud)

完整示例


Geo*_*vva 6

这是一个专门用于此目的的库: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`)
Run Code Online (Sandbox Code Playgroud)

它有详细的文档记录并且易于使用。

免责声明:我是这个库的作者。