golang使用泛型将SQL行扫描到任意类型的插件库scan的使用
Golang 使用泛型将 SQL 行扫描到任意类型的插件库 scan 的使用
简介
Scan 是一个 Golang 包,它提供了一种方便灵活的方式,利用泛型的强大功能将 SQL 行扫描到任何类型中。
特性
- 高效且可重用:避免重复代码,在一个地方定义列映射
- 自动关闭:无需担心资源泄漏
- 无反射:比基于反射的映射器更快
- 健壮的错误处理:管理错误的最佳实践
使用示例
基本用法
首先定义你的数据结构,然后创建列映射:
import "github.com/wroge/scan"
type Author struct {
ID int64
Name string
}
type Post struct {
ID int64
Title string
Authors []Author
}
// 定义数据库列到结构体字段的映射
var columns = scan.Columns[Post]{
// 将 'id' 列映射到 'Post' 结构体中的 'ID' 字段
// 使用 'scan.Any' 函数直接赋值,无需额外处理
"id": scan.Any(func(p *Post, id int64) { p.ID = id }),
// 将 'title' 列映射到 'Post' 结构体中的 'Title' 字段
// 'scan.Null' 函数允许处理可为空的数据库列
// 如果 'title' 列为 null,则使用 'default title' 作为值
"title": scan.Null("default title", func(p *Post, title string) { p.Title = title }),
// 将 'authors' 列(预期为 JSON 格式)映射到 'Post' 结构体中的 'Authors' 字段
// 'scan.JSON' 函数自动将 JSON 数据解组到 'Author' 结构体切片中
"authors": scan.JSON(func(p *Post, authors []Author) { p.Authors = authors }),
// 或者你可以用这个函数创建一个自定义扫描器
// "column": scan.Func[Post, V](func(p *Post, value V) error {
// return nil
// }),
}
rows, err := db.Query("SELECT ...")
// 处理错误
扫描所有行
posts, err := scan.All(rows, columns)
// 处理错误
扫描第一行
post, err := scan.First(rows, columns)
if err != nil {
if errors.Is(err, scan.ErrNoRows) {
// 处理无行情况
}
// 处理其他错误
}
扫描恰好一行
post, err := scan.One(rows, columns)
if err != nil {
if errors.Is(err, scan.ErrTooManyRows) {
// 处理行数过多的情况
// post 是有效的
}
if errors.Is(err, scan.ErrNoRows) {
// 处理无行情况
}
// 处理其他错误
}
扫描有限数量的行
posts, err := scan.Limit(10, rows, columns)
if err != nil {
if errors.Is(err, scan.ErrTooManyRows) {
// 如果结果集超过 10 行则忽略
// len(posts) == 10
}
// 处理其他错误
}
直接使用迭代器
iter, err := scan.Iter(rows, columns)
// 处理错误
defer iter.Close()
for iter.Next() {
var post Post
err = iter.Scan(&post)
// 处理错误
// 或者使用 Value 方法:
post, err := iter.Value()
// 处理错误
}
这个库提供了一种类型安全且高效的方式来处理数据库查询结果,避免了传统 ORM 或反射方法的许多缺点。通过使用泛型,它能够在编译时捕获类型错误,同时保持代码的简洁性和可读性。
更多关于golang使用泛型将SQL行扫描到任意类型的插件库scan的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang使用泛型将SQL行扫描到任意类型的插件库scan的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
使用泛型将SQL行扫描到任意类型 - scan库详解
在Go语言中,使用database/sql
包时,我们需要手动将SQL查询结果扫描到结构体或变量中,这个过程通常比较繁琐。scan
是一个优秀的第三方库,它利用Go 1.18引入的泛型特性,简化了这个过程。
scan库简介
scan
库提供了一种类型安全的方式来将SQL行扫描到Go结构体、切片或基本类型中。主要特点包括:
- 支持泛型,编译时类型检查
- 自动映射列名到结构体字段
- 支持嵌套结构体
- 轻量级且高性能
安装
go get github.com/georgysavva/scany/v2
基本用法
1. 扫描单行到结构体
package main
import (
"context"
"database/sql"
"fmt"
"log"
"github.com/georgysavva/scany/v2/dbscan"
_ "github.com/lib/pq"
)
type User struct {
ID int
Name string
Email string
Age int
IsActive bool
}
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 创建scan适配器
adapter := dbscan.NewDBScanAdapter(db)
// 查询单行
var user User
err = adapter.ScanOne(context.Background(), &user, "SELECT * FROM users WHERE id = $1", 1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %+v\n", user)
}
2. 扫描多行到切片
func getActiveUsers(db *sql.DB) ([]User, error) {
adapter := dbscan.NewDBScanAdapter(db)
var users []User
err := adapter.ScanAll(context.Background(), &users, "SELECT * FROM users WHERE is_active = true")
if err != nil {
return nil, err
}
return users, nil
}
3. 扫描到基本类型
func countUsers(db *sql.DB) (int, error) {
adapter := dbscan.NewDBScanAdapter(db)
var count int
err := adapter.ScanOne(context.Background(), &count, "SELECT COUNT(*) FROM users")
if err != nil {
return 0, err
}
return count, nil
}
高级特性
自定义列名映射
默认情况下,scan会将蛇形列名(user_name)映射到驼峰结构体字段(UserName)。如果需要自定义:
type Product struct {
ID int `db:"product_id"`
Name string `db:"product_name"`
Price float64
Description string `db:"-"`
}
// 使用自定义标签
opts := &dbscan.ScanOptions{
TagName: "db",
}
adapter := dbscan.NewDBScanAdapter(db, dbscan.WithScanOptions(opts))
处理NULL值
type NullableUser struct {
ID int
Name sql.NullString
BirthDate sql.NullTime
Rating sql.NullFloat64
}
嵌套结构体
type Address struct {
Street string
City string
Country string
}
type Customer struct {
ID int
Name string
Address Address `db:"address_"`
}
// 查询需要返回address_street, address_city, address_country列
性能考虑
scan库在性能上做了优化:
- 使用反射但缓存反射结果
- 预分配切片内存
- 最小化内存分配
对于极高性能要求的场景,仍然可以考虑手写扫描代码,但对于大多数应用,scan的性能损失可以忽略不计。
错误处理
scan会返回详细的错误信息,包括:
- 类型不匹配
- 缺少字段
- 数据库错误
- NULL值处理错误
建议始终检查返回的错误并进行适当处理。
总结
scan
库通过Go泛型提供了类型安全、简洁的SQL行扫描方案,大大减少了样板代码。它特别适合:
- 快速开发CRUD应用
- 需要处理复杂嵌套结构的场景
- 想要减少错误和提高代码可读性的项目
虽然学习曲线略高于直接使用database/sql
,但长期来看能显著提高开发效率和代码质量。