【问题标题】:How do I convert a database row into a struct如何将数据库行转换为结构
【发布时间】:2013-06-20 08:51:09
【问题描述】:

假设我有一个结构:

type User struct {
    Name  string
    Id    int
    Score int
}

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

【问题讨论】:

    标签: sql go


    【解决方案1】:

    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)
    

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

    【讨论】:

    • 如果简单的方法是将列手动绑定到结构字段我想知道什么是困难的方法
    • 不幸的是,这不是很方便,尤其是在较大结构的情况下 - 手动绑定到结构属性完全失败...使用 jmoiron/sqlx 或其他一些库更有效...
    • 我一直没有从 rows.Scan() 中得到任何结果。所有变量都设置为空。
    • 这里的管道语法到底是什么? “选择|人”?我在 godocs 中没有看到任何引用。
    • 和布赖恩有同样的 WTF 时刻。原来它是一个他们为测试 sql/database (golang.org/src/database/sql/fakedb_test.go) 制作的假驱动程序,我真的希望我可以将它用于新代码!
    【解决方案2】:

    我推荐github.com/jmoiron/sqlx

    来自自述文件:

    sqlx 是一个库,它在 go 的标准上提供了一组扩展 database/sql 图书馆。 sql.DBsql.TX的sqlx版本, sql.Stmt,等。都保持底层接口不变,所以 他们的接口是标准接口的超集。这使得 使用集成现有代码库相对容易 带有 sqlx 的数据库/sql。

    主要的附加概念是:

    • 将行编组为结构(具有嵌入式结构支持)、映射和切片
    • 命名参数支持,包括准备好的语句
    • GetSelect 快速从查询转到结构/切片

    自述文件还包括一个代码 sn-p 演示将一行扫描到结构中:

    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 字段)。您可以在the documentation 中阅读更多相关信息。

    【讨论】:

      【解决方案3】:

      这是一种方法 - 只需在 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
          }
      }
      

      【讨论】:

      • @eslammostafa 这段代码在什么时候会出现 NULL 值问题?
      • @deFreitas at score 例如,我的意思是来自数据库的空值。
      • @eslammostafa 我明白了,这真的很痛苦
      • @deFreitas 同时我使用 sql.NullString 和 sql.NullInt64 ..etc 来处理空值,不过这是一些额外的工作。
      • @eslammostafa 如果您使用sql.NullString 并将结构转换为JSON 则存在问题,则生成的不友好,需要使用VO 或类似的东西
      【解决方案4】:
      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)
      }
      

      Full example

      【讨论】:

        【解决方案5】:

        为此提供了一个包:sqlstruct

        不幸的是,上次我检查它不支持嵌入式结构(这对你自己来说是微不足道的 - 我在几个小时内就有了一个工作原型)。

        刚刚提交了我对sqlstruct所做的更改

        【讨论】:

          【解决方案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`)
          

          文档齐全且易于使用。

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

          【讨论】:

            【解决方案7】:

            使用: 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
            }
            

            【讨论】:

              猜你喜欢
              • 2011-07-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-09-21
              • 1970-01-01
              • 1970-01-01
              • 2023-02-08
              • 2012-02-19
              相关资源
              最近更新 更多