golang高效扫描数据库数据到Go结构体的插件库scany的使用

Golang高效扫描数据库数据到Go结构体的插件库scany的使用

概述

Go语言崇尚简洁,通常直接通过驱动程序与数据库交互而不使用ORM。这提供了对查询的完全控制和高效性,但也带来了问题:需要手动遍历数据库行并将所有列的数据扫描到对应的目标中。这个过程容易出错、冗长且繁琐。scany旨在解决这个问题,它允许开发者只需一次函数调用就能将复杂数据从数据库扫描到Go结构体和其他复合类型中,而无需处理行迭代。

scany不限于任何特定数据库,它与database/sql集成,因此支持任何具有database/sql驱动的数据库。它还支持pgx库的原生接口。

注意:scany不是ORM。它只在一个方向上工作:将数据从数据库扫描到Go对象中,但不能基于这些对象构建数据库查询。其次,它不知道对象之间的关系(如一对多、多对多)。

特性

  • 通过结构体标签自定义数据库列名
  • 通过嵌套或嵌入重用结构体
  • 支持NULL和自定义类型
  • 可忽略的结构体字段
  • 除了结构体,还支持map和Go基本类型作为目标
  • 可覆盖默认设置

安装

go get github.com/georgysavva/scany/v2

与database/sql一起使用

package main

import (
	"context"
	"database/sql"

	"github.com/georgysavva/scany/v2/sqlscan"
)

type User struct {
	ID    string
	Name  string
	Email string
	Age   int
}

func main() {
	ctx := context.Background()
	db, _ := sql.Open("postgres", "example-connection-url")

	var users []*User
	sqlscan.Select(ctx, db, &users, `SELECT id, name, email, age FROM users`)
	// users变量现在包含所有行的数据
}

使用sqlscan包与database/sql标准库一起工作。

与pgx原生接口一起使用

package main

import (
	"context"

	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/georgysavva/scany/v2/pgxscan"
)

type User struct {
	ID    string
	Name  string
	Email string
	Age   int
}

func main() {
	ctx := context.Background()
	db, _ := pgxpool.New(ctx, "example-connection-url")

	var users []*User
	pgxscan.Select(ctx, db, &users, `SELECT id, name, email, age FROM users`)
	// users变量现在包含所有行的数据
}

使用pgxscan包与pgx库的原生接口一起工作。

与其他数据库库一起使用

使用dbscan包,它可以与抽象数据库一起工作,并且可以与任何具有行概念的库集成。这个特定的包实现了scany的核心功能并包含所有逻辑。sqlscanpgxscan都在内部使用dbscan

与sqlx的比较

  • sqlx仅适用于database/sql标准库。scany不限于database/sql,它还支持pgx原生接口,并且可以扩展以独立于database/sql与任何数据库库一起工作
  • 在扫描和映射能力方面,scany提供了sqlx的所有特性
  • scany具有更简单的API和更少的概念,因此更容易上手

项目文档

有关详细的项目文档,请参见GitHub Wiki。

如何贡献

  • 如果你有想法或问题,只需提交拉取请求或问题。我们欢迎任何反馈
  • 如果你想帮忙但不知道怎么做。所有你可以处理的问题都标记为"help wanted"

许可证

本项目根据MIT许可证的条款授权。


更多关于golang高效扫描数据库数据到Go结构体的插件库scany的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高效扫描数据库数据到Go结构体的插件库scany的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用scany高效扫描数据库数据到Go结构体

scany是一个轻量级的Go库,用于将数据库行高效地扫描到Go结构体、映射或切片中。它是对标准库database/sql的补充,提供了更便捷的数据映射功能。

scany的主要特点

  1. 支持将SQL查询结果映射到结构体、映射和基本类型
  2. 自动处理列名与结构体字段名的映射
  3. 支持嵌套结构体
  4. 性能高效,几乎零开销
  5. 与标准库database/sql无缝集成

安装scany

go get github.com/georgysavva/scany/v2

基本用法

1. 映射到结构体

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
	"github.com/georgysavva/scany/v2/dbscan"
	"github.com/georgysavva/scany/v2/sqlscan"
)

type User struct {
	ID       int    `db:"id"`
	Username string `db:"username"`
	Email    string `db:"email"`
	Age      int    `db:"age"`
}

func main() {
	// 初始化数据库连接
	db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 查询单条记录到结构体
	var user User
	err = sqlscan.Get(context.Background(), db, &user, "SELECT id, username, email, age FROM users WHERE id = $1", 1)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("User: %+v\n", user)

	// 查询多条记录到结构体切片
	var users []*User
	err = sqlscan.Select(context.Background(), db, &users, "SELECT id, username, email, age FROM users WHERE age > $1", 18)
	if err != nil {
		log.Fatal(err)
	}
	for _, u := range users {
		fmt.Printf("User: %+v\n", u)
	}
}

2. 映射到map

// 查询单条记录到map
var userMap map[string]interface{}
err = sqlscan.Get(context.Background(), db, &userMap, "SELECT id, username, email, age FROM users WHERE id = $1", 1)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("User map: %+v\n", userMap)

// 查询多条记录到map切片
var usersMap []map[string]interface{}
err = sqlscan.Select(context.Background(), db, &usersMap, "SELECT id, username, email, age FROM users WHERE age > $1", 18)
if err != nil {
	log.Fatal(err)
}
for _, um := range usersMap {
	fmt.Printf("User map: %+v\n", um)
}

3. 自定义列名映射

scany默认使用db标签来映射列名到结构体字段。如果没有db标签,它会将列名转换为小写并与字段名匹配。

type Product struct {
	ID          int     `db:"product_id"`  // 显式指定列名
	Name        string  // 自动匹配为"name"
	Price       float64 `db:"price"`
	Description string  `db:"-"` // 忽略此字段
}

4. 处理嵌套结构体

type Address struct {
	Street  string `db:"street"`
	City    string `db:"city"`
	Country string `db:"country"`
}

type UserWithAddress struct {
	User
	Address Address `db:"address"` // 嵌套结构体
}

// 查询时需要使用JOIN获取地址信息
var userWithAddr UserWithAddress
err = sqlscan.Get(context.Background(), db, &userWithAddr, `
	SELECT 
		u.id, u.username, u.email, u.age,
		a.street, a.city, a.country
	FROM users u
	JOIN addresses a ON u.id = a.user_id
	WHERE u.id = $1
`, 1)

高级用法

1. 自定义扫描器

type NullString struct {
	sql.NullString
}

func (ns *NullString) Scan(value interface{}) error {
	return ns.NullString.Scan(value)
}

type UserWithNullString struct {
	ID       int        `db:"id"`
	Username string     `db:"username"`
	Bio      NullString `db:"bio"`
}

// 使用自定义扫描器处理NULL值
var userWithBio UserWithNullString
err = sqlscan.Get(context.Background(), db, &userWithBio, "SELECT id, username, bio FROM users WHERE id = $1", 1)

2. 使用dbscan API进行更灵活的控制

// 创建dbscan API实例
api, err := dbscan.NewAPI()
if err != nil {
	log.Fatal(err)
}

// 执行查询
rows, err := db.QueryContext(context.Background(), "SELECT id, username, email, age FROM users")
if err != nil {
	log.Fatal(err)
}
defer rows.Close()

// 手动扫描到结构体切片
var users []User
err = api.ScanAll(&users, rows)
if err != nil {
	log.Fatal(err)
}

3. 性能优化建议

  1. 预编译查询语句并使用sql.Stmt
  2. 对于大量数据,考虑分批处理
  3. 只查询需要的列
  4. 对于频繁使用的查询,考虑缓存结果

与类似库的比较

  1. sqlx: 功能更全面但更重量级,scany更轻量专注
  2. gorm: ORM框架,scany只是映射工具
  3. 标准库database/sql: scany提供了更便捷的映射功能

scany特别适合那些已经使用标准库database/sql但需要更便捷数据映射功能的项目。

总结

scany是一个简单高效的数据库扫描工具,它通过提供简洁的API和灵活的配置选项,大大简化了从数据库到Go结构体的映射过程。它的轻量级设计和与标准库的无缝集成使其成为许多项目的理想选择。

回到顶部