Database 如何使中的扫描DB行变干?
我在数据库中有一个包含用户帐户信息的表。我有一个名为User defined的结构Database 如何使中的扫描DB行变干?,database,go,Database,Go,我在数据库中有一个包含用户帐户信息的表。我有一个名为User defined的结构 type User struct { Id uint Username string Password string FirstName string LastName string Address1 string Address2 string .... a bunch more fields ... } 对于获取单个用户帐户,我定义了一个方法 func
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
}
系统中有几个地方需要获取用户信息,作为更大查询的一部分。也就是说,我正在获取一些其他类型的对象,但也获取与之相关的用户帐户
这意味着,我必须一遍又一遍地重复整个行。扫描(&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的用户部分扫描到用户结构中,方法与上面的用户方法相同。为了避免重复扫描
*sql.Rows
结构所需的步骤,可以引入两个接口。描述*sql.Rows
和*sql.Row
已经实现的行为的代码
// This interface is already implemented by *sql.Rows and *sql.Row.
type Row interface {
Scan(...interface{}) 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)
}
很好的结果似乎是我需要对Row和Rows使用两种不同的方法,因为它们是不同的类型。否则,这是一种提取行扫描的好方法。谢谢。@MadWombat我不知道你说的两种不同的方法是什么意思。“行和行”是指*sql.Row和*sql.Rows?是的,所以在接口中我想我需要实现ScanRow(Row sql.Row)和ScanRows(Rows sql.Rows)以适应查询和查询的结果。如果您定义了位于我答案顶部的接口(
键入Row interface{
),您不需要这样做,它由*sql.Row
和*sql.Rows
实现,您可以使用它作为ScanRow的参数,即ScanRow(r行)错误
。尝试一下,当您将*sql.Row和*sql.Rows传递给ScanRow时,您的程序将很好。请记住,in-Go接口是隐式满足的,这意味着您可以为已经存在的类型定义接口,而无需修改它们。啊!我明白了。谢谢。
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)
}