差不多:没有。
Query 方法将返回一个指向 Rows 结构的指针:
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
如果你打印那个 ( fmt.Printf("%#v\n", rows)) 你会看到这样的东西:
&sql.Rows{dc:(*sql.driverConn)(0xc8201225a0), releaseConn:(func(error)(0x4802c0), rowsi:(*pq.rows)(0xc820166700), closed:false, lastcols:[]driver.Value(nil), lasterr:error(nil), closeStmt:driver.Stmt(nil)}
...可能不是你想要的。
这些对应于sql 包中的 Rows 结构(您会注意到字段未导出):
type Rows struct {
dc *driverConn // owned; must call releaseConn when closed to release
releaseConn func(error)
rowsi driver.Rows
closed bool
lastcols []driver.Value
lasterr error // non-nil only if closed is true
closeStmt driver.Stmt // if non-nil, statement to Close on close
}
你会看到 []driver.Value(来自驱动程序包的一个接口),看起来我们可以期望找到一些有用的,甚至是人类可读的数据。但是当直接打印时它似乎没有用,它甚至是空的......所以你必须以某种方式获取底层信息。sql 包为我们提供了 Next 方法:
Next 准备下一个结果行以使用 Scan 方法读取。成功时返回 true,如果没有下一个结果行或在准备它时发生错误,则返回 false。应咨询 Err 以区分这两种情况。
对 Scan 的每次调用,即使是第一个调用,都必须在调用 Next 之前。
接下来将制作一个 []driver.Value 与我拥有的列数相同的大小,它可以通过 driver.Rows(rowsi 字段)访问(在 sql 包中),并用查询中的值填充它。
在调用 rows.Next() 之后,如果你做了同样的事情,fmt.Printf("%#v\n", rows) 你现在应该看到 []diver.Value 不再是空的,但它仍然不是你可以阅读的任何东西,更可能是类似的东西:[]diver.Value{[]uint8{0x47, 0x65...
而且由于该字段未导出,您甚至无法尝试将其转换为更有意义的内容。但是 sql 包为我们提供了一种对数据进行处理的方法,即 Scan。
Scan 方法非常简洁,有冗长的注释,我不会在此处粘贴,但真正重要的一点是它涵盖了您从 Next 方法和调用获得的当前行中的列convertAssign(dest[i], sv),您可以在此处看到: https ://golang.org/src/database/sql/convert.go
它很长但实际上相对简单,它本质上是切换源和目标的类型,并在可能的地方进行转换,然后从源复制到目标;函数注释告诉我们:
convertAssign 将 src 中的值复制到 dest,如果可能,将其转换。如果副本会导致信息丢失,则会返回错误。dest 应该是一个指针类型。
所以现在你有了一个方法 (Scan),你可以直接调用它,并返回转换后的值。您上面的代码示例很好(除了可能在 Scan 错误时调用 Fatal() )。
重要的是要意识到 sql 包必须与特定的驱动程序一起使用,而该驱动程序又是为特定的数据库软件实现的,因此在幕后进行了相当多的工作。
我认为如果你想隐藏/概括整个 Query() ---> Next() ---> Scan() 习惯用法,你最好的选择是将它放到另一个在幕后执行的函数中......写一个您在其中抽象出更高级别的实现的包,因为 sql 包抽象了一些特定于驱动程序的细节,转换和复制,填充行等。