为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
慕容森 2023-05-08 14:39:21
我在数据库中有一个包含用户帐户信息的表。我有一个名为用户定义的结构。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 sJOIN user AS u ON (u.id = s.user_id)JOIN some_other_table AS st1 ON (site.id = st1.site_id)... more SQL ...我有一个嵌入用户结构的站点结构方法。这里不想重复用户扫码。我想调用一个函数,它将 raw 的用户部分扫描到用户结构中,就像在上面的用户方法中一样。
查看完整描述

1 回答

?
莫回无

TA贡献1865条经验 获得超7个赞

为了消除扫描结构所需步骤的重复,*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 one

type RowScanner interface {

    ScanRow(Row) error

}

RowScanner 接口的示例实现如下所示:


type User struct {

    Id       uint

    Username string

    // ...

}


// Implements RowScanner

func (u *User) ScanRow(r Row) error {

    return r.Scan(

        &u.Id,

        &u.Username,

        // ...

    )

}


type UserList struct {

    Items []*User

}


// Implements RowScanner

func (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...))

}


// example

ulist := new(UserList)

if err := queryRows(queryString, ulist, arg1, arg2); err != nil {

    panic(err)

}


// or

u := 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 type

type Site struct {

    Id   uint

    Name string

    // ...

}


func (s *Site) ScannableFields() []interface{} {

    return []interface{}{

        &p.Id,

        &p.Name,

        // ...

    }

}


// Implements RowScanner

func (s *Site) ScanRow(r Row) error {

    return r.Scan(s.ScannableFields()...)

}


// your composite

type UserWithSite struct {

    User *User

    Site *Site

}


// Implements RowScanner

func (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 db

u := new(UserWithSite)

if err := queryRow(queryString, u, arg1, arg2); err != nil {

    panic(err)

}



查看完整回答
反对 回复 2023-05-08
  • 1 回答
  • 0 关注
  • 71 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信