我在数据库中有一个包含用户帐户信息的表。我有一个称为用户定义的结构。
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以及类似的库。我故意避免使用这些方法,因为它们依赖于具有详细记录的性能问题的反射包。而且我打算使用这些技术来扫描数百万行(不是数百万用户,但是这个问题扩展到其他记录类型)。
编辑:是的,我知道我应该编写一个函数。我不确定该函数应采用什么参数以及应返回什么结果。可以说,我要容纳的另一个查询看起来像这样
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 ...
我有一个嵌入用户结构的站点结构方法。我不想在这里重复用户扫描代码。我想调用一个函数,该函数会将原始文件的用户部分扫描到用户结构中,就像在上述用户方法中一样。
答案 0 :(得分: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 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)
}