golang将数据直接扫描到结构体、切片或基本类型的插件库scan的使用
Golang 将数据直接扫描到结构体、切片或基本类型的插件库 scan 的使用
Scan 是一个 Golang 库,可以直接将标准库 database/sql 的 rows 扫描到结构体、切片或基本类型中。
安装
import "github.com/blockloop/scan/v2"
使用示例
扫描多行到结构体切片
db, err := sql.Open("sqlite3", "database.sqlite")
rows, err := db.Query("SELECT * FROM persons")
var persons []Person
err := scan.Rows(&persons, rows)
fmt.Printf("%#v", persons)
// []Person{
// {ID: 1, Name: "brett"},
// {ID: 2, Name: "fred"},
// {ID: 3, Name: "stacy"},
// }
扫描多行到基本类型切片
rows, err := db.Query("SELECT name FROM persons")
var names []string
err := scan.Rows(&names, rows)
fmt.Printf("%#v", names)
// []string{
// "brett",
// "fred",
// "stacy",
// }
扫描单行到结构体
rows, err := db.Query("SELECT * FROM persons where name = 'brett' LIMIT 1")
var person Person
err := scan.Row(&person, rows)
fmt.Printf("%#v", person)
// Person{ ID: 1, Name: "brett" }
扫描标量值
rows, err := db.Query("SELECT age FROM persons where name = 'brett' LIMIT 1")
var age int8
err := scan.Row(&age, rows)
fmt.Printf("%d", age)
// 100
嵌套结构体字段 (v2.0.0+)
rows, err := db.Query(`
SELECT person.id,person.name,company.name FROM person
JOIN company on company.id = person.company_id
LIMIT 1
`)
var person struct {
ID int `db:"person.id"`
Name string `db:"person.name"`
Company struct {
Name string `db:"company.name"`
}
}
err = scan.RowStrict(&person, rows)
err = json.NewEncoder(os.Stdout).Encode(&person)
// Output:
// {"ID":1,"Name":"brett","Company":{"Name":"costco"}}
自定义列映射
默认情况下,列名使用基本的标题大小写转换映射到数据库列名。您可以通过设置 ColumnsMapper
和 ScannerMapper
为自定义函数来覆盖此行为。
严格扫描
Rows
和 Row
都有严格的替代方法,允许根据它们的 db
标签严格扫描到结构体。使用 RowsStrict
或 RowStrict
可以在不使用字段名称的情况下进行扫描。任何未使用 db
标签标记的字段都将被忽略,即使找到与字段名称匹配的列。
列
Columns
扫描结构体并返回基于 db
标签或结构体字段名称的假定列名的字符串切片。为了避免假设,使用 ColumnsStrict
,它只会返回用 db
标签标记的字段。
type User struct {
ID int64
Name string
Age int
BirthDate string `db:"bday"`
Zipcode string `db:"-"`
Store struct {
ID int
// ...
}
}
var nobody = new(User)
var userInsertCols = scan.Columns(nobody, "ID")
// []string{ "Name", "Age", "bday" }
var userSelectCols = scan.Columns(nobody)
// []string{ "ID", "Name", "Age", "bday" }
值
Values
扫描结构体并返回与提供的列关联的值。Values 使用 sync.Map
来缓存结构体的字段,以大大提高扫描类型的性能。
user := &User{
ID: 1,
Name: "Brett",
Age: 100,
}
vals := scan.Values([]string{"ID", "Name"}, user)
// []interface{}{ 1, "Brett" }
配置
AutoClose: 扫描完成后自动调用 rows.Close()
(默认为 true)
为什么使用 scan
虽然许多其他项目支持类似的功能(如 sqlx),但 scan 允许您使用任何数据库库,如标准库或 squirrel 来编写流畅的 SQL 语句,并将生成的 rows
传递给 scan
进行扫描。
更多关于golang将数据直接扫描到结构体、切片或基本类型的插件库scan的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang将数据直接扫描到结构体、切片或基本类型的插件库scan的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 数据扫描插件库 scan 使用指南
scan
是一个轻量级的 Go 语言库,用于将数据库查询结果直接扫描到结构体、切片或基本类型中,简化了数据库操作的数据映射过程。
安装
go get github.com/blockloop/scan
基本用法
1. 扫描到结构体
package main
import (
"database/sql"
"fmt"
"github.com/blockloop/scan"
_ "github.com/mattn/go-sqlite3"
)
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
func main() {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
defer db.Close()
// 创建表
_, err = db.Exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER
);
INSERT INTO users (name, age) VALUES ('Alice', 25);
INSERT INTO users (name, age) VALUES ('Bob', 30);
`)
if err != nil {
panic(err)
}
// 查询并扫描到结构体
rows, err := db.Query("SELECT * FROM users WHERE id = ?", 1)
if err != nil {
panic(err)
}
defer rows.Close()
var user User
if err := scan.Row(&user, rows); err != nil {
panic(err)
}
fmt.Printf("%+v\n", user) // 输出: {ID:1 Name:Alice Age:25}
}
2. 扫描到切片
// 查询所有用户并扫描到切片
rows, err := db.Query("SELECT * FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
var users []User
if err := scan.Rows(&users, rows); err != nil {
panic(err)
}
fmt.Printf("%+v\n", users)
// 输出: [{ID:1 Name:Alice Age:25} {ID:2 Name:Bob Age:30}]
3. 扫描到基本类型
// 扫描单个值
rows, err := db.Query("SELECT COUNT(*) FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
var count int
if err := scan.Row(&count, rows); err != nil {
panic(err)
}
fmt.Println("Total users:", count) // 输出: Total users: 2
高级特性
1. 自定义列名映射
默认情况下,scan
使用 db
标签或字段名的小写形式作为列名映射。你也可以自定义映射函数:
scan.SetMapper(func(s string) string {
// 自定义映射逻辑
return strings.ToUpper(s)
})
2. 处理 NULL 值
type UserWithNullable struct {
ID int `db:"id"`
Name string `db:"name"`
Age sql.NullInt64 `db:"age"` // 可空整数
Email sql.NullString `db:"email"` // 可空字符串
}
3. 使用指针接收结果
var userPtr *User
if err := scan.Row(&userPtr, rows); err != nil {
panic(err)
}
性能考虑
scan
库在性能上做了优化:
- 使用反射但缓存了反射结果
- 支持预编译扫描器
- 比标准库的
sql.Rows.Scan()
更高效
与标准库对比
标准库方式:
rows, err := db.Query("SELECT id, name, age FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Age); err != nil {
panic(err)
}
users = append(users, u)
}
scan
库方式:
rows, err := db.Query("SELECT id, name, age FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
var users []User
if err := scan.Rows(&users, rows); err != nil {
panic(err)
}
总结
scan
库提供了简洁的 API 将数据库查询结果映射到 Go 数据结构中,减少了样板代码,提高了开发效率。它特别适合在以下场景使用:
- 需要快速将查询结果映射到结构体
- 处理多行结果到切片
- 需要简洁的代码风格
对于更复杂的需求,也可以考虑使用更完整的 ORM 库如 GORM 或 XORM。