如何在 Go DRY 中扫描数据库行?

我在数据库中有一个包含用户帐户信息的表。我有一个名为用户定义的结构。


type User struct {

  Id        uint

  Username  string

  Password  string

  FirstName string

  LastName  string

  Address1  string

  Address2  string

  .... a bunch more fields ...

}

为了获取个人用户帐户,我定义了一个方法


func (user *User) GetById(db *sql.DB, id uint) error {

  query := `SELECT 

            ...a whole bunch of SQL ...

            WHERE id = $1

            ... more SQL ...

            LIMIT 1`

  row := db.QueryRow(query, id)

  err := row.Scan(

    &user.Id,

    &user.UserName,

    &user.Password,

    &user.FirstName,

    &user.LastName,

    ... some 20 more lines of fields read into the struct ...

  )

  if err != nil {

    return err

  }

  return nil

}

在系统中有几个地方我需要获取用户信息作为更大查询的一部分。也就是说,我正在获取一些其他类型的对象,还有一个与之相关的用户帐户。


这意味着,我必须rows.Scan(&user.Username, &user...)一遍又一遍地重复整个过程,这会占用整个页面,而且很容易出错,如果我更改了用户表结构,我将不得不更改很多地方的代码。我怎样才能让它更干燥?


编辑:我不确定为什么将其标记为重复,但由于需要进行此编辑,我将尝试再解释一次。我不是在问如何将一行扫描到一个结构中。正如上面的代码清楚地显示的那样,我已经知道该怎么做。我在问如何构建结构扫描代码,这样我就不必每次扫描相同类型的结构时都重复扫描代码的同一页。


编辑:另外,是的,我知道 sqlstruct 和 sqlx 以及类似的库。我故意避免这些,因为它们依赖于 reflect 包,并有详细记录的性能问题。我打算使用这些技术潜在地扫描数百万行(不是数百万用户,但这个问题扩展到其他记录类型)。


编辑:所以,是的,我知道我应该写一个函数。我不确定这个函数应该将什么作为参数以及它应该返回什么结果。可以说我想要容纳的另一个查询看起来像这样


SELECT

    s.id,

    s.name,

    ... more site fields ...

    u.id,

    u.username,

    ... more user fields ...

FROM site AS s

JOIN user AS u ON (u.id = s.user_id)

JOIN some_other_table AS st1 ON (site.id = st1.site_id)

... more SQL ...

我有一个嵌入用户结构的站点结构方法。这里不想重复用户扫码。我想调用一个函数,它将 raw 的用户部分扫描到用户结构中,就像在上面的用户方法中一样。


慕容森
浏览 72回答 1
1回答

莫回无

为了消除扫描结构所需步骤的重复,*sql.Rows您可以引入两个接口。一个描述*sql.Rows和的已经实现的行为*sql.Row。// This interface is already implemented by *sql.Rows and *sql.Row.type Row interface {    Scan(...interface{}) error}另一个抽象出行的实际扫描步骤。// have your entity types implement this onetype RowScanner interface {    ScanRow(Row) error}RowScanner 接口的示例实现如下所示:type User struct {    Id       uint    Username string    // ...}// Implements RowScannerfunc (u *User) ScanRow(r Row) error {    return r.Scan(        &u.Id,        &u.Username,        // ...    )}type UserList struct {    Items []*User}// Implements RowScannerfunc (list *UserList) ScanRow(r Row) error {    u := new(User)    if err := u.ScanRow(r); err != nil {        return err    }    list.Items = append(list.Items, u)    return nil}使用这些接口,您现在可以为所有通过使用这两个函数实现 RowScanner 接口的类型干燥行扫描代码。func queryRows(query string, rs RowScanner, params ...interface{}) error {    rows, err := db.Query(query, params...)    if err != nil {        return err    }    defer rows.Close()    for rows.Next() {        if err := rs.ScanRow(rows); err != nil {            return err        }    }    return rows.Err()}func queryRow(query string, rs RowScanner, params ...interface{}) error {    return rs.ScanRow(db.QueryRow(query, params...))}// exampleulist := new(UserList)if err := queryRows(queryString, ulist, arg1, arg2); err != nil {    panic(err)}// oru := new(User)if err := queryRow(queryString, u, arg1, arg2); err != nil {    panic(err)}如果您有想要扫描的复合类型,但又想避免重复枚举其元素的字段,那么您可以引入一种返回类型字段的方法,并在需要的地方重用该方法。例如:func (u *User) ScannableFields() []interface{} {    return []interface{}{        &u.Id,        &u.Username,        // ...    }}func (u *User) ScanRow(r Row) error {    return r.Scan(u.ScannableFields()...)}// your other entity typetype Site struct {    Id   uint    Name string    // ...}func (s *Site) ScannableFields() []interface{} {    return []interface{}{        &p.Id,        &p.Name,        // ...    }}// Implements RowScannerfunc (s *Site) ScanRow(r Row) error {    return r.Scan(s.ScannableFields()...)}// your compositetype UserWithSite struct {    User *User    Site *Site}// Implements RowScannerfunc (u *UserWithSite) ScanRow(r Row) error {    u.User = new(User)    u.Site = new(Site)    fields := append(u.User.ScannableFields(), u.Site.ScannableFields()...)    return r.Scan(fields...)}// retrieve from dbu := new(UserWithSite)if err := queryRow(queryString, u, arg1, arg2); err != nil {    panic(err)}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go