sqlx 是 Go 言语中一个流行的第三方包,它供给了对 Go 规范库 database/sql
的扩展,旨在简化和改进 Go 言语中运用 SQL 的体验,并供给了愈加强大的数据库交互功用。sqlx
保留了 database/sql
接口不变,是 database/sql
的超集,这使得将现有项目中运用的 database/sql
替换为 sqlx
变得相当轻松。
本文重点解说 sqlx
在 database/sql
基础上扩展的功用,关于 database/sql
已经支撑的功用则不会详细解说。假如你对 database/sql
不熟悉,可以检查我的另一篇文章《在 Go 中怎么运用 database/sql 来操作数据库》。
装置
sqlx
装置办法同 Go 言语中其他第三方包相同:
$ go get github.com/jmoiron/sqlx
sqlx 类型规划
sqlx
的规划与 database/sql
不同不大,编码风格较为一致,参阅 database/sql
规范库,sqlx
供给了如下几种与之对应的数据类型:
-
sqlx.DB
:相似于sql.DB
,表明数据库目标,可以用来操作数据库。 -
sqlx.Tx
:相似于sql.Tx
,业务目标。 -
sqlx.Stmt
:相似于sql.Stmt
,预处理 SQL 句子。 -
sqlx.NamedStmt
:对sqlx.Stmt
的封装,支撑签字参数。 -
sqlx.Rows
:相似于sql.Rows
,sqlx.Queryx
的回来成果。 -
sqlx.Row
:相似于sql.Row
,sqlx.QueryRowx
的回来成果。
以上类型与 database/sql
供给的对应类型在功用上差异不大,但 sqlx
为这些类型供给了更友好的办法。
预备
为了演示 sqlx
用法,我预备了如下 MySQL 数据库表:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL COMMENT '用户名',
`email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',
`age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年纪',
`birthday` datetime DEFAULT NULL COMMENT '生日',
`salary` varchar(128) DEFAULT NULL COMMENT '薪水',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `u_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
你可以运用 MySQL 指令行或图形化工具创立这张表。
衔接数据库
运用 sqlx
衔接数据库:
package main
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
func main() {
var (
db *sqlx.DB
err error
dsn = "user:password@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
)
// 1. 运用 sqlx.Open 衔接数据库
db, err = sqlx.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
// 2. 运用 sqlx.Open 变体办法 sqlx.MustOpen 衔接数据库,假如出现过错直接 panic
db = sqlx.MustOpen("mysql", dsn)
// 3. 假如已经有了 *sql.DB 目标,则可以运用 sqlx.NewDb 衔接数据库,得到 *sqlx.DB 目标
sqlDB, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
db = sqlx.NewDb(sqlDB, "mysql")
// 4. 运用 sqlx.Connect 衔接数据库,等价于 sqlx.Open + db.Ping
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
log.Fatal(err)
}
// 5. 运用 sqlx.Connect 变体办法 sqlx.MustConnect 衔接数据库,假如出现过错直接 panic
db = sqlx.MustConnect("mysql", dsn)
}
在 sqlx
中咱们可以经过以上 5 种办法衔接数据库。
sqlx.Open
对标 sql.Open
办法,回来 *sqlx.DB
类型。
sqlx.MustOpen
与 sqlx.Open
相同会回来 *sqlx.DB
实例,但假如遇到过错则会 panic
。
sqlx.NewDb
支撑从一个 database/sql
包的 *sql.DB
目标创立一个新的 *sqlx.DB
类型,而且需求指定驱动称号。
运用前 3 种办法衔接数据库并不会当即与数据库树立衔接,衔接将会在合适的时候延迟树立。为了保证可以正常衔接数据库,往往需求调用 db.Ping()
办法进行验证:
ctx := context.Background()
if err := db.PingContext(ctx); err != nil {
log.Fatal(err)
}
sqlx
供给的 sqlx.Connect
办法便是用来简化这一操作的,它等价于 sqlx.Open
+ db.Ping
两个办法,其界说如下:
func Connect(driverName, dataSourceName string) (*DB, error) {
db, err := Open(driverName, dataSourceName)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
db.Close()
return nil, err
}
return db, nil
}
sqlx.MustConnect
办法在 sqlx.Connect
办法的基础上,供给了遇到过错当即 panic
的功用。看到 sqlx.MustConnect
办法的界说你就理解了:
func MustConnect(driverName, dataSourceName string) *DB {
db, err := Connect(driverName, dataSourceName)
if err != nil {
panic(err)
}
return db
}
以后当你遇见 MustXxx
相似办法名时就应该想到,其功用往往等价于 Xxx
办法,不过在其内部完成中,遇到 error
不再回来,而是直接进行 panic
,这也是 Go 言语很多库中的惯用办法。
声明模型
咱们界说一个 User
结构体来映射数据库中的 user
表:
type User struct {
ID int
Name sql.NullString `json:"username"`
Email string
Age int
Birthday time.Time
Salary Salary
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type Salary struct {
Month int `json:"month"`
Year int `json:"year"`
}
// Scan implements sql.Scanner, use custom types in *sql.Rows.Scan
func (s *Salary) Scan(src any) error {
if src == nil {
return nil
}
var buf []byte
switch v := src.(type) {
case []byte:
buf = v
case string:
buf = []byte(v)
default:
return fmt.Errorf("invalid type: %T", src)
}
err := json.Unmarshal(buf, s)
return err
}
// Value implements driver.Valuer, use custom types in Query/QueryRow/Exec
func (s Salary) Value() (driver.Value, error) {
v, err := json.Marshal(s)
return string(v), err
}
User
结构体在这儿可以被称为「模型」。
履行 SQL 指令
database/sql
包供给了 *sql.DB.Exec
办法来履行一条 SQL 指令,sqlx
对其进行了扩展,供给了 *sqlx.DB.MustExec
办法来履行一条 SQL 指令:
func MustCreateUser(db *sqlx.DB) (int64, error) {
birthday := time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)
user := User{
Name: sql.NullString{String: "jianghushinian", Valid: true},
Email: "jianghushinian007@outlook.com",
Age: 10,
Birthday: birthday,
Salary: Salary{
Month: 100000,
Year: 10000000,
},
}
res := db.MustExec(
`INSERT INTO user(name, email, age, birthday, salary) VALUES(?, ?, ?, ?, ?)`,
user.Name, user.Email, user.Age, user.Birthday, user.Salary,
)
return res.LastInsertId()
}
这儿运用 *sqlx.DB.MustExec
办法插入了一条 user
记载。
*sqlx.DB.MustExec
办法界说如下:
func (db *DB) MustExec(query string, args ...interface{}) sql.Result {
return MustExec(db, query, args...)
}
func MustExec(e Execer, query string, args ...interface{}) sql.Result {
res, err := e.Exec(query, args...)
if err != nil {
panic(err)
}
return res
}
与前文介绍的 sqlx.MustOpen
办法相同,*sqlx.DB.MustExec
办法也会在遇到过错时直接 panic
,其内部调用的是 *sqlx.DB.Exec
办法。
履行 SQL 查询
database/sql
包供给了 *sql.DB.Query
和 *sql.DB.QueryRow
两个查询办法,其签名如下:
func (db *DB) Query(query string, args ...any) (*Rows, error)
func (db *DB) QueryRow(query string, args ...any) *Row
sqlx
在这两个办法的基础上,扩展出如下两个办法:
func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryRowx(query string, args ...interface{}) *Row
这两个办法回来的类型正是前文 sqlx 类型规划 中提到的 sqlx.Rows
、sqlx.Row
类型。
下面来解说下这两个办法怎么运用。
Queryx
运用 *sqlx.DB.Queryx
办法查询记载如下:
func QueryxUsers(db *sqlx.DB) ([]User, error) {
var us []User
rows, err := db.Queryx("SELECT * FROM user")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var u User
// sqlx 供给了快捷办法可以将查询成果直接扫描到结构体
err = rows.StructScan(&u)
if err != nil {
return nil, err
}
us = append(us, u)
}
return us, nil
}
*sqlx.DB.Queryx
办法签名尽管与 *sql.DB.Query
办法根本相同,但它回来类型 *sqlx.Rows
得到了扩展,其供给的 StructScan
办法可以便利的将查询成果直接扫描到 User
结构体,这极大的增加了便携性,咱们再也不必像运用 *sql.Rows
供给的 Scan
办法那样挨个写出 User
的特点了。
QueryRowx
运用 *sqlx.DB.QueryRowx
办法查询记载如下:
func QueryRowxUser(db *sqlx.DB, id int) (User, error) {
var u User
err := db.QueryRowx("SELECT * FROM user WHERE id = ?", id).StructScan(&u)
return u, err
}
*sqlx.Row
相同供给了 StructScan
办法将查询成果扫描到结构体。
另外,这儿运用了链式调用的办法,在调用 db.QueryRowx()
之后直接调用了 .StructScan(&u)
,接纳的 err
是 StructScan
的回来成果。这是因为 db.QueryRowx()
的回来成果 *sqlx.Row
中记载了过错信息 err
,假如查询阶段遇到过错会被记载到 *sqlx.Row.err
中。在调用 StructScan
办法阶段,其内部首先判断 r.err != nil
,假如存在 err
直接回来过错,没有过错则将查询成果扫描到 dest
参数接纳到的结构体指针,代码完成如下:
type Row struct {
err error
unsafe bool
rows *sql.Rows
Mapper *reflectx.Mapper
}
func (r *Row) StructScan(dest interface{}) error {
return r.scanAny(dest, true)
}
func (r *Row) scanAny(dest interface{}, structOnly bool) error {
if r.err != nil {
return r.err
}
...
}
sqlx
不仅扩展了 *sql.DB.Query
和 *sql.DB.QueryRow
两个查询办法,它还新增了两个查询办法:
func (db *DB) Get(dest interface{}, query string, args ...interface{}) error
func (db *DB) Select(dest interface{}, query string, args ...interface{}) error
*sqlx.DB.Get
办法包装了 *sqlx.DB.QueryRowx
办法,用以简化查询单条记载。
*sqlx.DB.Select
办法包装了 *sqlx.DB.Queryx
办法,用以简化查询多条记载。
接下来解说这两个办法怎么运用。
Get
运用 *sqlx.DB.Get
办法查询记载如下:
func GetUser(db *sqlx.DB, id int) (User, error) {
var u User
// 查询记载扫描数据到 struct
err := db.Get(&u, "SELECT * FROM user WHERE id = ?", id)
return u, err
}
可以发现 *sqlx.DB.Get
办法用起来非常简略,咱们不再需求调用 StructScan
办法将查询成果扫描到结构体中,只需求将结构体指针当作 Get
办法的第一个参数传递进去即可。
其代码完成如下:
func (db *DB) Get(dest interface{}, query string, args ...interface{}) error {
return Get(db, dest, query, args...)
}
func Get(q Queryer, dest interface{}, query string, args ...interface{}) error {
r := q.QueryRowx(query, args...)
return r.scanAny(dest, false)
}
依据源码可以看出,*sqlx.DB.Get
内部调用了 *sqlx.DB.QueryRowx
办法。
Select
运用 *sqlx.DB.Select
办法查询记载如下:
func SelectUsers(db *sqlx.DB) ([]User, error) {
var us []User
// 查询记载扫描数据到 slice
err := db.Select(&us, "SELECT * FROM user")
return us, err
}
可以发现 *sqlx.DB.Select
办法用起来相同非常简略,它可以直接将查询成果扫描到 []User
切片中。
其代码完成如下:
func (db *DB) Select(dest interface{}, query string, args ...interface{}) error {
return Select(db, dest, query, args...)
}
func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
rows, err := q.Queryx(query, args...)
if err != nil {
return err
}
// if something happens here, we want to make sure the rows are Closed
defer rows.Close()
return scanAll(rows, dest, false)
}
依据源码可以看出,*sqlx.DB.Select
内部调用了 *sqlx.DB.Queryx
办法。
sqlx.In
在 database/sql
中假如想要履行 SQL IN 查询,由于 IN 查询参数长度不固定,咱们不得不运用 fmt.Sprintf
来动态拼接 SQL 句子,以保证 SQL 中参数占位符的个数是正确的。
sqlx
供给了 In
办法来支撑 SQL IN 查询,这极大的简化了代码,也使得代码更易维护和安全。
运用示例如下:
func SqlxIn(db *sqlx.DB, ids []int64) ([]User, error) {
query, args, err := sqlx.In("SELECT * FROM user WHERE id IN (?)", ids)
if err != nil {
return nil, err
}
query = db.Rebind(query)
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var us []User
for rows.Next() {
var user User
err = rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age,
&user.Birthday, &user.Salary, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return nil, err
}
us = append(us, user)
}
return us, nil
}
调用 sqlx.In
并传递 SQL 句子以及切片类型的参数,它将回来新的查询 SQL query
以及参数 args
,这个 query
将会依据 ids
来动态调整。
比方咱们传递 ids
为 []int64{1, 2, 3}
,则得到 query
为 SELECT * FROM user WHERE id IN (?, ?, ?)
。
注意,咱们接下来又调用 db.Rebind(query)
从头绑定了 query
变量的参数占位符。假如你运用 MySQL 数据库,这不是必须的,因为咱们运用的 MySQL 驱动程序参数占位符便是 ?
。而假如你运用 PostgreSQL 数据库,由于 PostgreSQL 驱动程序参数占位符是 $n
,这时就必须要调用 db.Rebind(query)
办法来转换参数占位符了。
它会将 SELECT * FROM user WHERE id IN (?, ?, ?)
中的参数占位符转换为 PostgreSQL 驱动程序可以辨认的参数占位符 SELECT * FROM user WHERE id IN ($1, $2, $3)
。
之后的代码就跟运用 database/sql
查询记载没什么两样了。
运用签字参数
sqlx
供给了两个办法 NamedExec
、NamedQuery
,它们可以支撑签字参数 :name
,这样就不必再运用 ?
这种占位符的方式了。
这两个办法签名如下:
func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error)
func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error)
其运用示例如下:
func NamedExec(db *sqlx.DB) error {
m := map[string]interface{}{
"email": "jianghushinian007@outlook.com",
"age": 18,
}
result, err := db.NamedExec(`UPDATE user SET age = :age WHERE email = :email`, m)
if err != nil {
return err
}
fmt.Println(result.RowsAffected())
return nil
}
func NamedQuery(db *sqlx.DB) ([]User, error) {
u := User{
Email: "jianghushinian007@outlook.com",
Age: 18,
}
rows, err := db.NamedQuery("SELECT * FROM user WHERE email = :email OR age = :age", u)
if err != nil {
return nil, err
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err := rows.StructScan(&user)
if err != nil {
return nil, err
}
users = append(users, user)
}
return users, nil
}
咱们可以运用 :name
的办法来命名参数,它可以匹配 map
或 struct
对应字段的参数值,这样的 SQL 句子可读性更强。
业务
在业务的支撑上,sqlx
扩展出了 Must
版别的业务,运用示例如下:
func MustTransaction(db *sqlx.DB) error {
tx := db.MustBegin()
tx.MustExec("UPDATE user SET age = 25 WHERE id = ?", 1)
return tx.Commit()
}
不过这种用法不多,你知道就行。以下是业务的引荐用法:
func Transaction(db *sqlx.DB, id int64, name string) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
res, err := tx.Exec("UPDATE user SET name = ? WHERE id = ?", name, id)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
fmt.Printf("rowsAffected: %d\n", rowsAffected)
return tx.Commit()
}
咱们运用 defer
句子来处理业务的回滚操作,这样就不必在每次处理过错时重复的编写调用 tx.Rollback()
的代码。
假如代码正常履行到最后,经过 tx.Commit()
来提交业务,此时即使再调用 tx.Rollback()
也不会对成果产生影响。
预处理句子
sqlx
针对 *sql.DB.Prepare
扩展出了 *sqlx.DB.Preparex
办法,回来 *sqlx.Stmt
类型。
*sqlx.Stmt
类型支撑 Queryx
、QueryRowx
、Get
、Select
这些 sqlx
特有的办法。
其运用示例如下:
func PreparexGetUser(db *sqlx.DB) (User, error) {
stmt, err := db.Preparex(`SELECT * FROM user WHERE id = ?`)
if err != nil {
return User{}, err
}
var u User
err = stmt.Get(&u, 1)
return u, err
}
*sqlx.DB.Preparex
办法界说如下:
func Preparex(p Preparer, query string) (*Stmt, error) {
s, err := p.Prepare(query)
if err != nil {
return nil, err
}
return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
}
实际上 *sqlx.DB.Preparex
内部还是调用的 *sql.DB.Preapre
办法,只不过将其回来成果构造成 *sqlx.Stmt
类型并回来。
不安全的扫描
在运用 *sqlx.DB.Get
等办法查询记载时,假如 SQL 句子查询出来的字段与要绑定的模型特点不匹配,则会报错。
示例如下:
func GetUser(db *sqlx.DB) (User, error) {
var user struct {
ID int
Name string
Email string
// 没有 Age 特点
}
err := db.Get(&user, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
if err != nil {
return User{}, err
}
return User{
ID: user.ID,
Name: sql.NullString{String: user.Name},
Email: user.Email,
}, nil
}
以上示例代码中,SQL 句子中查询了 id
、name
、email
、age
4 个字段,而 user
结构体则只要 ID
、Name
、Email
3 个特点,由于无法一一对应,履行以上代码,咱们将得到如下报错信息:
missing destination name age in *struct { ID int; Name string; Email string }
这种表现是合理的,契合 Go 言语的编程风格,尽早暴露过错有助于减少代码存在 BUG 的隐患。
不过,有些时候,咱们便是为了便利想要让上面的示例代码可以运行,可以这样做:
func UnsafeGetUser(db *sqlx.DB) (User, error) {
var user struct {
ID int
Name string
Email string
// 没有 Age 特点
}
udb := db.Unsafe()
err := udb.Get(&user, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
if err != nil {
return User{}, err
}
return User{
ID: user.ID,
Name: sql.NullString{String: user.Name},
Email: user.Email,
}, nil
}
这儿咱们不再直接运用 db.Get
来查询记载,而是先经过 udb := db.Unsafe()
获取 unsafe
特点为 true
的 *sqlx.DB
目标,然后再调用它的 Get
办法。
*sqlx.DB
界说如下:
type DB struct {
*sql.DB
driverName string
unsafe bool
Mapper *reflectx.Mapper
}
当 unsafe
特点为 true
时,*sqlx.DB
目标会疏忽不匹配的字段,使代码可以正常运行,并将可以匹配的字段正确绑定到 user
结构体目标上。
经过这个特点的称号咱们就知道,这是不安全的做法,不被引荐。
与未运用的变量相同,被疏忽的列是对网络和数据库资源的糟蹋,而且这很简单导致出现模型与数据库表不匹配而不被感知的状况。
Scan 变体
前文示例中,咱们见过了 *sqlx.Rows.Scan
的变体 *sqlx.Rows.StructScan
的用法,它可以便利的将查询成果扫描到 struct
中。
sqlx
还供给了 *sqlx.Rows.MapScan
、*sqlx.Rows.SliceScan
两个办法,可以将查询成果别离扫描到 map
和 slice
中。
运用示例如下:
func MapScan(db *sqlx.DB) ([]map[string]interface{}, error) {
rows, err := db.Queryx("SELECT * FROM user")
if err != nil {
return nil, err
}
defer rows.Close()
var res []map[string]interface{}
for rows.Next() {
r := make(map[string]interface{})
err := rows.MapScan(r)
if err != nil {
return nil, err
}
res = append(res, r)
}
return res, err
}
func SliceScan(db *sqlx.DB) ([][]interface{}, error) {
rows, err := db.Queryx("SELECT * FROM user")
if err != nil {
return nil, err
}
defer rows.Close()
var res [][]interface{}
for rows.Next() {
// cols is an []interface{} of all the column results
cols, err := rows.SliceScan()
if err != nil {
return nil, err
}
res = append(res, cols)
}
return res, err
}
其中,rows.MapScan(r)
用法与 rows.StructScan(&u)
用法相似,都是将接纳查询成果集的目标模型指针变量当作参数传递进来。
rows.SliceScan()
用法略有不同,它不接纳参数,而是将成果保存在 []interface{}
中并回来。
可以按需运用以上两个办法。
操控字段称号映射
讲到这儿,想必不少同学心里可能存在一个疑问,rows.StructScan(&u)
在将查询记载的字段映射到对应结构体特点时,是怎么找到对应联系的呢?
答案便是 db
结构体标签。
回顾前文讲 声明模型 时,User
结构体中界说的 CreatedAt
、UpdatedAt
两个字段,界说如下:
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
这儿显式的标明晰结构体标签 db
,sqlx
正是运用 db
标签来映射查询字段和模型特点。
默许状况下,结构体字段会被映射成全小写方式,如 ID
字段会被映射为 id
,而 CreatedAt
字段会被映射为 createdat
。
因为在 user
数据库表中,创立时刻和更新时刻两个字段别离为 created_at
、updated_at
,与 sqlx
默许字段映射规则不匹配,所以我才显式的为 CreatedAt
和 UpdatedAt
两个字段指明晰 db
标签,这样 sqlx
的 rows.StructScan
就能正常工作了。
当然,数据库字段不一定都是小写,假如你的数据库字段为全大写,sqlx
供给了 *sqlx.DB.MapperFunc
办法来操控查询字段和模型特点的映射联系。
其运用示例如下:
func MapperFuncUseToUpper(db *sqlx.DB) (User, error) {
copyDB := sqlx.NewDb(db.DB, db.DriverName())
copyDB.MapperFunc(strings.ToUpper)
var user User
err := copyDB.Get(&user, "SELECT id as ID, name as NAME, email as EMAIL FROM user WHERE id = ?", 1)
if err != nil {
return User{}, err
}
return user, nil
}
这儿为了不改变原有的 db
目标,咱们复制了一个 copyDB
,调用 copyDB.MapperFunc
并将 strings.ToUpper
传递进来。
注意这儿的查询句子中,查询字段全部经过 as
从头命名成了大写方式,而 User
模型字段 db
默许都为小写方式。
copyDB.MapperFunc(strings.ToUpper)
的作用,便是在调用 Get
办法将查询成果扫描到结构体时,把 User
模型的小写字段,经过 strings.ToUpper
办法转成大写,这样查询字段和模型特点就全为大写了,也就可以一一匹配上了。
还有一种状况,假如你的模型已存在 json
标签,而且不想重复的再抄一遍到 db
标签,咱们可以直接运用 json
标签来映射查询字段和模型特点。
func MapperFuncUseJsonTag(db *sqlx.DB) (User, error) {
copyDB := sqlx.NewDb(db.DB, db.DriverName())
// Create a new mapper which will use the struct field tag "json" instead of "db"
copyDB.Mapper = reflectx.NewMapperFunc("json", strings.ToLower)
var user User
// json tag
err := copyDB.Get(&user, "SELECT id, name as username, email FROM user WHERE id = ?", 1)
if err != nil {
return User{}, err
}
return user, nil
}
这儿需求直接修改 copyDB.Mapper
特点,赋值为 reflectx.NewMapperFunc("json", strings.ToLower)
将模型映射的标签由 db
改为 json
,并经过 strings.ToLower
办法转换为小写。
reflectx
依照如下办法导入:
import "github.com/jmoiron/sqlx/reflectx"
现在,查询句子中 name
特点经过运用 as
被重命名为 username
,而 username
刚好与 User
模型中 Name
字段的 json
标签相对应:
Name sql.NullString `json:"username"`
所以,以上示例代码可以正确映射查询字段和模型特点。
总结
sqlx
树立在 database/sql
包之上,用于简化和增强与联系型数据库的交互操作。
对常见数据库操作办法,sqlx
供给了 Must
版别,如 sqlx.MustOpen
用来衔接数据库,*sqlx.DB.MustExec
用来履行 SQL 句子,当遇到 error
时将会直接 panic
。
sqlx
还扩展了查询办法 *sqlx.DB.Queryx
、*sqlx.DB.QueryRowx
、*sqlx.DB.Get
、*sqlx.DB.Select
,而且这些查询办法支撑直接将查询成果扫描到结构体。
sqlx
为 SQL IN 操作供给了快捷办法 sqlx.In
。
为了使 SQL 更易阅读,sqlx
供给了 *sqlx.DB.NamedExec
、*sqlx.DB.NamedQuery
两个办法支撑签字参数。
调用 *sqlx.DB.Unsafe()
办法可以获取 unsafe
特点为 true
的 *sqlx.DB
目标,在将查询成果扫描到结构体使可以用来疏忽不匹配的记载字段。
除了可以将查询成果扫描到 struct
,sqlx
还支撑将查询成果扫描到 map
和 slice
。
sqlx
运用 db
结构体标签来映射查询字段和模型特点,假如不显式指定 db
标签,默许映射的模型特点为小写方式,可以经过 *sqlx.DB.MapperFunc
函数来修改默许行为。
本文完好代码示例我放在了 GitHub 上,欢迎点击检查。
期望此文能对你有所帮助。
联系我
- 微信:jianghushinian
- 邮箱:jianghushinian007@outlook.com
- 博客地址:jianghushinian.cn/
参阅
- sqlx 源码:github.com/jmoiron/sql…
- sqlx 文档:pkg.go.dev/github.com/…
- sqlx 官网:jmoiron.github.io/sqlx/
- 本文示例代码:github.com/jianghushin…