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"}}

自定义列映射

默认情况下,列名使用基本的标题大小写转换映射到数据库列名。您可以通过设置 ColumnsMapperScannerMapper 为自定义函数来覆盖此行为。

严格扫描

RowsRow 都有严格的替代方法,允许根据它们的 db 标签严格扫描到结构体。使用 RowsStrictRowStrict 可以在不使用字段名称的情况下进行扫描。任何未使用 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

1 回复

更多关于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 库在性能上做了优化:

  1. 使用反射但缓存了反射结果
  2. 支持预编译扫描器
  3. 比标准库的 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 数据结构中,减少了样板代码,提高了开发效率。它特别适合在以下场景使用:

  1. 需要快速将查询结果映射到结构体
  2. 处理多行结果到切片
  3. 需要简洁的代码风格

对于更复杂的需求,也可以考虑使用更完整的 ORM 库如 GORM 或 XORM。

回到顶部