设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间10 现在时间10 缓存数据 大雨过后,彩虹深处,全世界的颜色都在我眼里,全世界的浪漫都在我手心,早安!加油!

大雨过后,彩虹深处,全世界的颜色都在我眼里,全世界的浪漫都在我手心,早安!加油!

查看: 1570|回复: 6

Go语言使用sqlx操作数据库的示例详解

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:204
  • 打卡月天数:0
  • 打卡总奖励:3221
  • 最近打卡:2023-08-27 04:32:00
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
446
主题
416
精华
0
金钱
4524
积分
887
注册时间
2022-12-25
最后登录
2025-5-31

发表于 2023-7-25 16:18:46 | 显示全部楼层 |阅读模式
sqlx 是 Go 语言中一个流行的第三方包,它提供了对 Go 标准库
  1. database/sql
复制代码
的扩展,旨在简化和改进 Go 语言中使用 SQL 的体验,并提供了更加强大的数据库交互功能。
  1. sqlx
复制代码
保留了
  1. database/sql
复制代码
接口不变,是
  1. database/sql
复制代码
的超集,这使得将现有项目中使用的
  1. database/sql
复制代码
替换为
  1. sqlx
复制代码
变得相当轻松。
本文重点讲解
  1. sqlx
复制代码
  1. database/sql
复制代码
基础上扩展的功能,对于
  1. database/sql
复制代码
已经支持的功能则不会详细讲解。如果你对
  1. database/sql
复制代码
不熟悉,可以查看我的另一篇文章《在 Go 中如何使用 database/sql 来操作数据库》。

安装
  1. sqlx
复制代码
安装方式同 Go 语言中其他第三方包一样:
  1. $ go get github.com/jmoiron/sqlx
复制代码
sqlx 类型设计
  1. sqlx
复制代码
的设计与
  1. database/sql
复制代码
差别不大,编码风格较为统一,参考
  1. database/sql
复制代码
标准库,
  1. sqlx
复制代码
提供了如下几种与之对应的数据类型:

    1. sqlx.DB
    复制代码
    :类似于
    1. sql.DB
    复制代码
    ,表示数据库对象,可以用来操作数据库。
    1. sqlx.Tx
    复制代码
    :类似于
    1. sql.Tx
    复制代码
    ,事务对象。
    1. sqlx.Stmt
    复制代码
    :类似于
    1. sql.Stmt
    复制代码
    ,预处理 SQL 语句。
    1. sqlx.NamedStmt
    复制代码
    :对
    1. sqlx.Stmt
    复制代码
    的封装,支持具名参数。
    1. sqlx.Rows
    复制代码
    :类似于
    1. sql.Rows
    复制代码
    1. sqlx.Queryx
    复制代码
    的返回结果。
    1. sqlx.Row
    复制代码
    :类似于
    1. sql.Row
    复制代码
    1. sqlx.QueryRowx
    复制代码
    的返回结果。
以上类型与
  1. database/sql
复制代码
提供的对应类型在功能上区别不大,但
  1. sqlx
复制代码
为这些类型提供了更友好的方法。

准备

为了演示
  1. sqlx
复制代码
用法,我准备了如下 MySQL 数据库表:
  1. DROP TABLE IF EXISTS `user`;
  2. CREATE TABLE `user` (
  3.   `id` int(11) NOT NULL AUTO_INCREMENT,
  4.   `name` varchar(50) DEFAULT NULL COMMENT '用户名',
  5.   `email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',
  6.   `age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
  7.   `birthday` datetime DEFAULT NULL COMMENT '生日',
  8.   `salary` varchar(128) DEFAULT NULL COMMENT '薪水',
  9.   `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  10.   `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  11.   PRIMARY KEY (`id`),
  12.   UNIQUE KEY `u_email` (`email`)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
复制代码
你可以使用 MySQL 命令行或图形化工具创建这张表。

连接数据库

使用
  1. sqlx
复制代码
连接数据库:
  1. package main
  2. import (
  3.         "database/sql"
  4.         "log"
  5.         _ "github.com/go-sql-driver/mysql"
  6.         "github.com/jmoiron/sqlx"
  7. )
  8. func main() {
  9.         var (
  10.                 db  *sqlx.DB
  11.                 err error
  12.                 dsn = "user:password@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
  13.         )
  14.         // 1. 使用 sqlx.Open 连接数据库
  15.         db, err = sqlx.Open("mysql", dsn)
  16.         if err != nil {
  17.                 log.Fatal(err)
  18.         }
  19.         // 2. 使用 sqlx.Open 变体方法 sqlx.MustOpen 连接数据库,如果出现错误直接 panic
  20.         db = sqlx.MustOpen("mysql", dsn)
  21.         // 3. 如果已经有了 *sql.DB 对象,则可以使用 sqlx.NewDb 连接数据库,得到 *sqlx.DB 对象
  22.         sqlDB, err := sql.Open("mysql", dsn)
  23.         if err != nil {
  24.                 log.Fatal(err)
  25.         }
  26.         db = sqlx.NewDb(sqlDB, "mysql")
  27.         // 4. 使用 sqlx.Connect 连接数据库,等价于 sqlx.Open + db.Ping
  28.         db, err = sqlx.Connect("mysql", dsn)
  29.         if err != nil {
  30.                 log.Fatal(err)
  31.         }
  32.         // 5. 使用 sqlx.Connect 变体方法 sqlx.MustConnect 连接数据库,如果出现错误直接 panic
  33.         db = sqlx.MustConnect("mysql", dsn)
  34. }
复制代码
  1. sqlx
复制代码
中我们可以通过以上 5 种方式连接数据库。
  1. sqlx.Open
复制代码
对标
  1. sql.Open
复制代码
方法,返回
  1. *sqlx.DB
复制代码
类型。
  1. sqlx.MustOpen
复制代码
  1. sqlx.Open
复制代码
一样会返回
  1. *sqlx.DB
复制代码
实例,但如果遇到错误则会
  1. panic
复制代码
  1. sqlx.NewDb
复制代码
支持从一个
  1. database/sql
复制代码
包的
  1. *sql.DB
复制代码
对象创建一个新的
  1. *sqlx.DB
复制代码
类型,并且需要指定驱动名称。
使用前 3 种方式连接数据库并不会立即与数据库建立连接,连接将会在合适的时候延迟建立。为了确保能够正常连接数据库,往往需要调用
  1. db.Ping()
复制代码
方法进行验证:
  1. ctx := context.Background()
  2. if err := db.PingContext(ctx); err != nil {
  3.         log.Fatal(err)
  4. }
复制代码
  1. sqlx
复制代码
提供的
  1. sqlx.Connect
复制代码
方法就是用来简化这一操作的,它等价于
  1. sqlx.Open
复制代码
+
  1. db.Ping
复制代码
两个方法,其定义如下:
  1. func Connect(driverName, dataSourceName string) (*DB, error) {
  2.         db, err := Open(driverName, dataSourceName)
  3.         if err != nil {
  4.                 return nil, err
  5.         }
  6.         err = db.Ping()
  7.         if err != nil {
  8.                 db.Close()
  9.                 return nil, err
  10.         }
  11.         return db, nil
  12. }
复制代码
  1. sqlx.MustConnect
复制代码
方法在
  1. sqlx.Connect
复制代码
方法的基础上,提供了遇到错误立即
  1. panic
复制代码
的功能。看到
  1. sqlx.MustConnect
复制代码
方法的定义你就明白了:
  1. func MustConnect(driverName, dataSourceName string) *DB {
  2.         db, err := Connect(driverName, dataSourceName)
  3.         if err != nil {
  4.                 panic(err)
  5.         }
  6.         return db
  7. }
复制代码
以后当你遇见
  1. MustXxx
复制代码
类似方法名时就应该想到,其功能往往等价于
  1. Xxx
复制代码
方法,不过在其内部实现中,遇到
  1. error
复制代码
不再返回,而是直接进行
  1. panic
复制代码
,这也是 Go 语言很多库中的惯用方法。

声明模型

我们定义一个
  1. User
复制代码
结构体来映射数据库中的
  1. user
复制代码
表:
  1. type User struct {
  2.         ID       int
  3.         Name     sql.NullString `json:"username"`
  4.         Email    string
  5.         Age      int
  6.         Birthday time.Time
  7.         Salary   Salary
  8.         CreatedAt time.Time `db:"created_at"`
  9.         UpdatedAt time.Time `db:"updated_at"`
  10. }
  11. type Salary struct {
  12.         Month int `json:"month"`
  13.         Year  int `json:"year"`
  14. }
  15. // Scan implements sql.Scanner, use custom types in *sql.Rows.Scan
  16. func (s *Salary) Scan(src any) error {
  17.         if src == nil {
  18.                 return nil
  19.         }
  20.         var buf []byte
  21.         switch v := src.(type) {
  22.         case []byte:
  23.                 buf = v
  24.         case string:
  25.                 buf = []byte(v)
  26.         default:
  27.                 return fmt.Errorf("invalid type: %T", src)
  28.         }
  29.         err := json.Unmarshal(buf, s)
  30.         return err
  31. }
  32. // Value implements driver.Valuer, use custom types in Query/QueryRow/Exec
  33. func (s Salary) Value() (driver.Value, error) {
  34.         v, err := json.Marshal(s)
  35.         return string(v), err
  36. }
复制代码
  1. User
复制代码
结构体在这里可以被称为「模型」。

执行 SQL 命令
  1. database/sql
复制代码
包提供了
  1. *sql.DB.Exec
复制代码
方法来执行一条 SQL 命令,
  1. sqlx
复制代码
对其进行了扩展,提供了
  1. *sqlx.DB.MustExec
复制代码
方法来执行一条 SQL 命令:
  1. func MustCreateUser(db *sqlx.DB) (int64, error) {
  2.         birthday := time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)
  3.         user := User{
  4.                 Name:     sql.NullString{String: "jianghushinian", Valid: true},
  5.                 Email:    "jianghushinian007@outlook.com",
  6.                 Age:      10,
  7.                 Birthday: birthday,
  8.                 Salary: Salary{
  9.                         Month: 100000,
  10.                         Year:  10000000,
  11.                 },
  12.         }
  13.         res := db.MustExec(
  14.                 `INSERT INTO user(name, email, age, birthday, salary) VALUES(?, ?, ?, ?, ?)`,
  15.                 user.Name, user.Email, user.Age, user.Birthday, user.Salary,
  16.         )
  17.         return res.LastInsertId()
  18. }
复制代码
这里使用
  1. *sqlx.DB.MustExec
复制代码
方法插入了一条
  1. user
复制代码
记录。
  1. *sqlx.DB.MustExec
复制代码
方法定义如下:
  1. func (db *DB) MustExec(query string, args ...interface{}) sql.Result {
  2.         return MustExec(db, query, args...)
  3. }
  4. func MustExec(e Execer, query string, args ...interface{}) sql.Result {
  5.         res, err := e.Exec(query, args...)
  6.         if err != nil {
  7.                 panic(err)
  8.         }
  9.         return res
  10. }
复制代码
与前文介绍的
  1. sqlx.MustOpen
复制代码
方法一样,
  1. *sqlx.DB.MustExec
复制代码
方法也会在遇到错误时直接
  1. panic
复制代码
,其内部调用的是
  1. *sqlx.DB.Exec
复制代码
方法。

执行 SQL 查询
  1. database/sql
复制代码
包提供了
  1. *sql.DB.Query
复制代码
  1. *sql.DB.QueryRow
复制代码
两个查询方法,其签名如下:
  1. func (db *DB) Query(query string, args ...any) (*Rows, error)
  2. func (db *DB) QueryRow(query string, args ...any) *Row
复制代码
  1. sqlx
复制代码
在这两个方法的基础上,扩展出如下两个方法:
  1. func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error)
  2. func (db *DB) QueryRowx(query string, args ...interface{}) *Row
复制代码
这两个方法返回的类型正是前文 sqlx 类型设计 中提到的
  1. sqlx.Rows
复制代码
  1. sqlx.Row
复制代码
类型。
下面来讲解下这两个方法如何使用。

Queryx

使用
  1. *sqlx.DB.Queryx
复制代码
方法查询记录如下:
  1. func QueryxUsers(db *sqlx.DB) ([]User, error) {
  2.         var us []User
  3.         rows, err := db.Queryx("SELECT * FROM user")
  4.         if err != nil {
  5.                 return nil, err
  6.         }
  7.         defer rows.Close()
  8.         for rows.Next() {
  9.                 var u User
  10.                 // sqlx 提供了便捷方法可以将查询结果直接扫描到结构体
  11.                 err = rows.StructScan(&u)
  12.                 if err != nil {
  13.                         return nil, err
  14.                 }
  15.                 us = append(us, u)
  16.         }
  17.         return us, nil
  18. }
复制代码
  1. *sqlx.DB.Queryx
复制代码
方法签名虽然与
  1. *sql.DB.Query
复制代码
方法基本相同,但它返回类型
  1. *sqlx.Rows
复制代码
得到了扩展,其提供的
  1. StructScan
复制代码
方法能够方便的将查询结果直接扫描到
  1. User
复制代码
结构体,这极大的增加了便携性,我们再也不用像使用
  1. *sql.Rows
复制代码
提供的
  1. Scan
复制代码
方法那样挨个写出
  1. User
复制代码
的属性了。

QueryRowx

使用
  1. *sqlx.DB.QueryRowx
复制代码
方法查询记录如下:
  1. func QueryRowxUser(db *sqlx.DB, id int) (User, error) {
  2.         var u User
  3.         err := db.QueryRowx("SELECT * FROM user WHERE id = ?", id).StructScan(&u)
  4.         return u, err
  5. }
复制代码
  1. *sqlx.Row
复制代码
同样提供了
  1. StructScan
复制代码
方法将查询结果扫描到结构体。
另外,这里使用了链式调用的方式,在调用
  1. db.QueryRowx()
复制代码
之后直接调用了
  1. .StructScan(&u)
复制代码
,接收的
  1. err
复制代码
  1. StructScan
复制代码
的返回结果。这是因为
  1. db.QueryRowx()
复制代码
的返回结果
  1. *sqlx.Row
复制代码
中记录了错误信息
  1. err
复制代码
,如果查询阶段遇到错误会被记录到
  1. *sqlx.Row.err
复制代码
中。在调用
  1. StructScan
复制代码
方法阶段,其内部首先判断
  1. r.err != nil
复制代码
,如果存在
  1. err
复制代码
直接返回错误,没有错误则将查询结果扫描到
  1. dest
复制代码
参数接收到的结构体指针,代码实现如下:
  1. type Row struct {
  2.         err    error
  3.         unsafe bool
  4.         rows   *sql.Rows
  5.         Mapper *reflectx.Mapper
  6. }
  7. func (r *Row) StructScan(dest interface{}) error {
  8.         return r.scanAny(dest, true)
  9. }
  10. func (r *Row) scanAny(dest interface{}, structOnly bool) error {
  11.         if r.err != nil {
  12.                 return r.err
  13.         }
  14.         ...
  15. }
复制代码
  1. sqlx
复制代码
不仅扩展了
  1. *sql.DB.Query
复制代码
  1. *sql.DB.QueryRow
复制代码
两个查询方法,它还新增了两个查询方法:
  1. func (db *DB) Get(dest interface{}, query string, args ...interface{}) error
  2. func (db *DB) Select(dest interface{}, query string, args ...interface{}) error
复制代码
  1. *sqlx.DB.Get
复制代码
方法包装了
  1. *sqlx.DB.QueryRowx
复制代码
方法,用以简化查询单条记录。
  1. *sqlx.DB.Select
复制代码
方法包装了
  1. *sqlx.DB.Queryx
复制代码
方法,用以简化查询多条记录。
接下来讲解这两个方法如何使用。

Get

使用
  1. *sqlx.DB.Get
复制代码
方法查询记录如下:
  1. func GetUser(db *sqlx.DB, id int) (User, error) {
  2.         var u User
  3.         // 查询记录扫描数据到 struct
  4.         err := db.Get(&u, "SELECT * FROM user WHERE id = ?", id)
  5.         return u, err
  6. }
复制代码
可以发现
  1. *sqlx.DB.Get
复制代码
方法用起来非常简单,我们不再需要调用
  1. StructScan
复制代码
方法将查询结果扫描到结构体中,只需要将结构体指针当作
  1. Get
复制代码
方法的第一个参数传递进去即可。
其代码实现如下:
  1. func (db *DB) Get(dest interface{}, query string, args ...interface{}) error {
  2.         return Get(db, dest, query, args...)
  3. }
  4. func Get(q Queryer, dest interface{}, query string, args ...interface{}) error {
  5.         r := q.QueryRowx(query, args...)
  6.         return r.scanAny(dest, false)
  7. }
复制代码
根据源码可以看出,
  1. *sqlx.DB.Get
复制代码
内部调用了
  1. *sqlx.DB.QueryRowx
复制代码
方法。

Select

使用
  1. *sqlx.DB.Select
复制代码
方法查询记录如下:
  1. func SelectUsers(db *sqlx.DB) ([]User, error) {
  2.         var us []User
  3.         // 查询记录扫描数据到 slice
  4.         err := db.Select(&us, "SELECT * FROM user")
  5.         return us, err
  6. }
复制代码
可以发现
  1. *sqlx.DB.Select
复制代码
方法用起来同样非常简单,它可以直接将查询结果扫描到
  1. []User
复制代码
切片中。
其代码实现如下:
  1. func (db *DB) Select(dest interface{}, query string, args ...interface{}) error {
  2.         return Select(db, dest, query, args...)
  3. }
  4. func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
  5.         rows, err := q.Queryx(query, args...)
  6.         if err != nil {
  7.                 return err
  8.         }
  9.         // if something happens here, we want to make sure the rows are Closed
  10.         defer rows.Close()
  11.         return scanAll(rows, dest, false)
  12. }
复制代码
根据源码可以看出,
  1. *sqlx.DB.Select
复制代码
内部调用了
  1. *sqlx.DB.Queryx
复制代码
方法。

sqlx.In

  1. database/sql
复制代码
中如果想要执行 SQL IN 查询,由于 IN 查询参数长度不固定,我们不得不使用
  1. fmt.Sprintf
复制代码
来动态拼接 SQL 语句,以保证 SQL 中参数占位符的个数是正确的。
  1. sqlx
复制代码
提供了
  1. In
复制代码
方法来支持 SQL IN 查询,这极大的简化了代码,也使得代码更易维护和安全。
使用示例如下:
  1. func SqlxIn(db *sqlx.DB, ids []int64) ([]User, error) {
  2.         query, args, err := sqlx.In("SELECT * FROM user WHERE id IN (?)", ids)
  3.         if err != nil {
  4.                 return nil, err
  5.         }
  6.         query = db.Rebind(query)
  7.         rows, err := db.Query(query, args...)
  8.         if err != nil {
  9.                 return nil, err
  10.         }
  11.         defer rows.Close()
  12.         var us []User
  13.         for rows.Next() {
  14.                 var user User
  15.                 err = rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age,
  16.                         &user.Birthday, &user.Salary, &user.CreatedAt, &user.UpdatedAt)
  17.                 if err != nil {
  18.                         return nil, err
  19.                 }
  20.                 us = append(us, user)
  21.         }
  22.         return us, nil
  23. }
复制代码
调用
  1. sqlx.In
复制代码
并传递 SQL 语句以及切片类型的参数,它将返回新的查询 SQL
  1. query
复制代码
以及参数
  1. args
复制代码
,这个
  1. query
复制代码
将会根据
  1. ids
复制代码
来动态调整。
比如我们传递
  1. ids
复制代码
  1. []int64{1, 2, 3}
复制代码
,则得到
  1. query
复制代码
  1. SELECT * FROM user WHERE id IN (?, ?, ?)
复制代码

注意,我们接下来又调用
  1. db.Rebind(query)
复制代码
重新绑定了
  1. query
复制代码
变量的参数占位符。如果你使用 MySQL 数据库,这不是必须的,因为我们使用的 MySQL 驱动程序参数占位符就是
  1. ?
复制代码
。而如果你使用 PostgreSQL 数据库,由于 PostgreSQL 驱动程序参数占位符是
  1. $n
复制代码
,这时就必须要调用
  1. db.Rebind(query)
复制代码
方法来转换参数占位符了。
它会将
  1. SELECT * FROM user WHERE id IN (?, ?, ?)
复制代码
中的参数占位符转换为 PostgreSQL 驱动程序能够识别的参数占位符
  1. SELECT * FROM user WHERE id IN ($1, $2, $3)
复制代码

之后的代码就跟使用
  1. database/sql
复制代码
查询记录没什么两样了。

使用具名参数
  1. sqlx
复制代码
提供了两个方法
  1. NamedExec
复制代码
  1. NamedQuery
复制代码
,它们能够支持具名参数
  1. :name
复制代码
,这样就不必再使用
  1. ?
复制代码
这种占位符的形式了。
这两个方法签名如下:
  1. func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error)
  2. func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error)
复制代码
其使用示例如下:
  1. func NamedExec(db *sqlx.DB) error {
  2.         m := map[string]interface{}{
  3.                 "email": "jianghushinian007@outlook.com",
  4.                 "age":   18,
  5.         }
  6.         result, err := db.NamedExec(`UPDATE user SET age = :age WHERE email = :email`, m)
  7.         if err != nil {
  8.                 return err
  9.         }
  10.         fmt.Println(result.RowsAffected())
  11.         return nil
  12. }
  13. func NamedQuery(db *sqlx.DB) ([]User, error) {
  14.         u := User{
  15.                 Email: "jianghushinian007@outlook.com",
  16.                 Age:   18,
  17.         }
  18.         rows, err := db.NamedQuery("SELECT * FROM user WHERE email = :email OR age = :age", u)
  19.         if err != nil {
  20.                 return nil, err
  21.         }
  22.         defer rows.Close()
  23.         var users []User
  24.         for rows.Next() {
  25.                 var user User
  26.                 err := rows.StructScan(&user)
  27.                 if err != nil {
  28.                         return nil, err
  29.                 }
  30.                 users = append(users, user)
  31.         }
  32.         return users, nil
  33. }
复制代码
我们可以使用
  1. :name
复制代码
的方式来命名参数,它能够匹配
  1. map
复制代码
  1. struct
复制代码
对应字段的参数值,这样的 SQL 语句可读性更强。

事务

在事务的支持上,
  1. sqlx
复制代码
扩展出了
  1. Must
复制代码
版本的事务,使用示例如下:
  1. func MustTransaction(db *sqlx.DB) error {
  2.         tx := db.MustBegin()
  3.         tx.MustExec("UPDATE user SET age = 25 WHERE id = ?", 1)
  4.         return tx.Commit()
  5. }
复制代码
不过这种用法不多,你知道就行。以下是事务的推荐用法:
  1. func Transaction(db *sqlx.DB, id int64, name string) error {
  2.         tx, err := db.Begin()
  3.         if err != nil {
  4.                 return err
  5.         }
  6.         defer tx.Rollback()
  7.         res, err := tx.Exec("UPDATE user SET name = ? WHERE id = ?", name, id)
  8.         if err != nil {
  9.                 return err
  10.         }
  11.         rowsAffected, err := res.RowsAffected()
  12.         if err != nil {
  13.                 return err
  14.         }
  15.         fmt.Printf("rowsAffected: %d\n", rowsAffected)
  16.         return tx.Commit()
  17. }
复制代码
我们使用
  1. defer
复制代码
语句来处理事务的回滚操作,这样就不必在每次处理错误时重复的编写调用
  1. tx.Rollback()
复制代码
的代码。
如果代码正常执行到最后,通过
  1. tx.Commit()
复制代码
来提交事务,此时即使再调用
  1. tx.Rollback()
复制代码
也不会对结果产生影响。

预处理语句
  1. sqlx
复制代码
针对
  1. *sql.DB.Prepare
复制代码
扩展出了
  1. *sqlx.DB.Preparex
复制代码
方法,返回
  1. *sqlx.Stmt
复制代码
类型。
  1. *sqlx.Stmt
复制代码
类型支持
  1. Queryx
复制代码
  1. QueryRowx
复制代码
  1. Get
复制代码
  1. Select
复制代码
这些
  1. sqlx
复制代码
特有的方法。
其使用示例如下:
  1. func PreparexGetUser(db *sqlx.DB) (User, error) {
  2.         stmt, err := db.Preparex(`SELECT * FROM user WHERE id = ?`)
  3.         if err != nil {
  4.                 return User{}, err
  5.         }
  6.         var u User
  7.         err = stmt.Get(&u, 1)
  8.         return u, err
  9. }
复制代码
  1. *sqlx.DB.Preparex
复制代码
方法定义如下:
  1. func Preparex(p Preparer, query string) (*Stmt, error) {
  2.         s, err := p.Prepare(query)
  3.         if err != nil {
  4.                 return nil, err
  5.         }
  6.         return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
  7. }
复制代码
实际上
  1. *sqlx.DB.Preparex
复制代码
内部还是调用的
  1. *sql.DB.Preapre
复制代码
方法,只不过将其返回结果构造成
  1. *sqlx.Stmt
复制代码
类型并返回。

不安全的扫描

在使用
  1. *sqlx.DB.Get
复制代码
等方法查询记录时,如果 SQL 语句查询出来的字段与要绑定的模型属性不匹配,则会报错。
示例如下:
  1. func GetUser(db *sqlx.DB) (User, error) {
  2.         var user struct {
  3.                 ID    int
  4.                 Name  string
  5.                 Email string
  6.                 // 没有 Age 属性
  7.         }
  8.         err := db.Get(&user, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
  9.         if err != nil {
  10.                 return User{}, err
  11.         }
  12.         return User{
  13.                 ID:    user.ID,
  14.                 Name:  sql.NullString{String: user.Name},
  15.                 Email: user.Email,
  16.         }, nil
  17. }
复制代码
以上示例代码中,SQL 语句中查询了
  1. id
复制代码
  1. name
复制代码
  1. email
复制代码
  1. age
复制代码
4 个字段,而
  1. user
复制代码
结构体则只有
  1. ID
复制代码
  1. Name
复制代码
  1. Email
复制代码
3 个属性,由于无法一一对应,执行以上代码,我们将得到如下报错信息:
  1. missing destination name age in *struct { ID int; Name string; Email string }
复制代码
这种表现是合理的,符合 Go 语言的编程风格,尽早暴露错误有助于减少代码存在 BUG 的隐患。
不过,有些时候,我们就是为了方便想要让上面的示例代码能够运行,可以这样做:
  1. func UnsafeGetUser(db *sqlx.DB) (User, error) {
  2.         var user struct {
  3.                 ID    int
  4.                 Name  string
  5.                 Email string
  6.                 // 没有 Age 属性
  7.         }
  8.         udb := db.Unsafe()
  9.         err := udb.Get(&user, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
  10.         if err != nil {
  11.                 return User{}, err
  12.         }
  13.         return User{
  14.                 ID:    user.ID,
  15.                 Name:  sql.NullString{String: user.Name},
  16.                 Email: user.Email,
  17.         }, nil
  18. }
复制代码
这里我们不再直接使用
  1. db.Get
复制代码
来查询记录,而是先通过
  1. udb := db.Unsafe()
复制代码
获取
  1. unsafe
复制代码
属性为
  1. true
复制代码
  1. *sqlx.DB
复制代码
对象,然后再调用它的
  1. Get
复制代码
方法。
  1. *sqlx.DB
复制代码
定义如下:
  1. type DB struct {
  2.         *sql.DB
  3.         driverName string
  4.         unsafe     bool
  5.         Mapper     *reflectx.Mapper
  6. }
复制代码
  1. unsafe
复制代码
属性为
  1. true
复制代码
时,
  1. *sqlx.DB
复制代码
对象会忽略不匹配的字段,使代码能够正常运行,并将能够匹配的字段正确绑定到
  1. user
复制代码
结构体对象上。
通过这个属性的名称我们就知道,这是不安全的做法,不被推荐。
与未使用的变量一样,被忽略的列是对网络和数据库资源的浪费,并且这很容易导致出现模型与数据库表不匹配而不被感知的情况。

Scan 变体

前文示例中,我们见过了
  1. *sqlx.Rows.Scan
复制代码
的变体
  1. *sqlx.Rows.StructScan
复制代码
的用法,它能够方便的将查询结果扫描到
  1. struct
复制代码
中。
  1. sqlx
复制代码
还提供了
  1. *sqlx.Rows.MapScan
复制代码
  1. *sqlx.Rows.SliceScan
复制代码
两个方法,能够将查询结果分别扫描到
  1. map
复制代码
  1. slice
复制代码
中。
使用示例如下:
  1. func MapScan(db *sqlx.DB) ([]map[string]interface{}, error) {
  2.         rows, err := db.Queryx("SELECT * FROM user")
  3.         if err != nil {
  4.                 return nil, err
  5.         }
  6.         defer rows.Close()
  7.         var res []map[string]interface{}
  8.         for rows.Next() {
  9.                 r := make(map[string]interface{})
  10.                 err := rows.MapScan(r)
  11.                 if err != nil {
  12.                         return nil, err
  13.                 }
  14.                 res = append(res, r)
  15.         }
  16.         return res, err
  17. }
  18. func SliceScan(db *sqlx.DB) ([][]interface{}, error) {
  19.         rows, err := db.Queryx("SELECT * FROM user")
  20.         if err != nil {
  21.                 return nil, err
  22.         }
  23.         defer rows.Close()
  24.         var res [][]interface{}
  25.         for rows.Next() {
  26.                 // cols is an []interface{} of all the column results
  27.                 cols, err := rows.SliceScan()
  28.                 if err != nil {
  29.                         return nil, err
  30.                 }
  31.                 res = append(res, cols)
  32.         }
  33.         return res, err
  34. }
复制代码
其中,
  1. rows.MapScan(r)
复制代码
用法与
  1. rows.StructScan(&u)
复制代码
用法类似,都是将接收查询结果集的目标模型指针变量当作参数传递进来。
  1. rows.SliceScan()
复制代码
用法略有不同,它不接收参数,而是将结果保存在
  1. []interface{}
复制代码
中并返回。
可以按需使用以上两个方法。

控制字段名称映射

讲到这里,想必不少同学心里可能存在一个疑惑,
  1. rows.StructScan(&u)
复制代码
在将查询记录的字段映射到对应结构体属性时,是如何找到对应关系的呢?
答案就是
  1. db
复制代码
结构体标签。
回顾前文讲 声明模型 时,
  1. User
复制代码
结构体中定义的
  1. CreatedAt
复制代码
  1. UpdatedAt
复制代码
两个字段,定义如下:
  1. CreatedAt time.Time `db:"created_at"`
  2. UpdatedAt time.Time `db:"updated_at"`
复制代码
这里显式的标明了结构体标签
  1. db
复制代码
  1. sqlx
复制代码
正是使用
  1. db
复制代码
标签来映射查询字段和模型属性。
默认情况下,结构体字段会被映射成全小写形式,如
  1. ID
复制代码
字段会被映射为
  1. id
复制代码
,而
  1. CreatedAt
复制代码
字段会被映射为
  1. createdat
复制代码

因为在
  1. user
复制代码
数据库表中,创建时间和更新时间两个字段分别为
  1. created_at
复制代码
  1. updated_at
复制代码
,与
  1. sqlx
复制代码
默认字段映射规则不匹配,所以我才显式的为
  1. CreatedAt
复制代码
  1. UpdatedAt
复制代码
两个字段指明了
  1. db
复制代码
标签,这样
  1. sqlx
复制代码
  1. rows.StructScan
复制代码
就能正常工作了。
当然,数据库字段不一定都是小写,如果你的数据库字段为全大写,
  1. sqlx
复制代码
提供了
  1. *sqlx.DB.MapperFunc
复制代码
方法来控制查询字段和模型属性的映射关系。
其使用示例如下:
  1. func MapperFuncUseToUpper(db *sqlx.DB) (User, error) {
  2.         copyDB := sqlx.NewDb(db.DB, db.DriverName())
  3.         copyDB.MapperFunc(strings.ToUpper)
  4.         var user User
  5.         err := copyDB.Get(&user, "SELECT id as ID, name as NAME, email as EMAIL FROM user WHERE id = ?", 1)
  6.         if err != nil {
  7.                 return User{}, err
  8.         }
  9.         return user, nil
  10. }
复制代码
这里为了不改变原有的
  1. db
复制代码
对象,我们复制了一个
  1. copyDB
复制代码
,调用
  1. copyDB.MapperFunc
复制代码
并将
  1. strings.ToUpper
复制代码
传递进来。
注意这里的查询语句中,查询字段全部通过
  1. as
复制代码
重新命名成了大写形式,而
  1. User
复制代码
模型字段
  1. db
复制代码
默认都为小写形式。
  1. copyDB.MapperFunc(strings.ToUpper)
复制代码
的作用,就是在调用
  1. Get
复制代码
方法将查询结果扫描到结构体时,把
  1. User
复制代码
模型的小写字段,通过
  1. strings.ToUpper
复制代码
方法转成大写,这样查询字段和模型属性就全为大写了,也就能够一一匹配上了。
还有一种情况,如果你的模型已存在
  1. json
复制代码
标签,并且不想重复的再抄一遍到
  1. db
复制代码
标签,我们可以直接使用
  1. json
复制代码
标签来映射查询字段和模型属性。
  1. func MapperFuncUseJsonTag(db *sqlx.DB) (User, error) {
  2.         copyDB := sqlx.NewDb(db.DB, db.DriverName())
  3.         // Create a new mapper which will use the struct field tag "json" instead of "db"
  4.         copyDB.Mapper = reflectx.NewMapperFunc("json", strings.ToLower)
  5.         var user User
  6.         // json tag
  7.         err := copyDB.Get(&user, "SELECT id, name as username, email FROM user WHERE id = ?", 1)
  8.         if err != nil {
  9.                 return User{}, err
  10.         }
  11.         return user, nil
  12. }
复制代码
这里需要直接修改
  1. copyDB.Mapper
复制代码
属性,赋值为
  1. reflectx.NewMapperFunc("json", strings.ToLower)
复制代码
将模型映射的标签由
  1. db
复制代码
改为
  1. json
复制代码
,并通过
  1. strings.ToLower
复制代码
方法转换为小写。
  1. reflectx
复制代码
按照如下方式导入:
  1. import "github.com/jmoiron/sqlx/reflectx"
复制代码
现在,查询语句中
  1. name
复制代码
属性通过使用
  1. as
复制代码
被重命名为
  1. username
复制代码
,而
  1. username
复制代码
刚好与
  1. User
复制代码
模型中
  1. Name
复制代码
字段的
  1. json
复制代码
标签相对应:
  1. Name     sql.NullString `json:"username"`
复制代码
所以,以上示例代码能够正确映射查询字段和模型属性。

总结
  1. sqlx
复制代码
建立在
  1. database/sql
复制代码
包之上,用于简化和增强与关系型数据库的交互操作。
对常见数据库操作方法,
  1. sqlx
复制代码
提供了
  1. Must
复制代码
版本,如
  1. sqlx.MustOpen
复制代码
用来连接数据库,
  1. *sqlx.DB.MustExec
复制代码
用来执行 SQL 语句,当遇到
  1. error
复制代码
时将会直接
  1. panic
复制代码
  1. sqlx
复制代码
还扩展了查询方法
  1. *sqlx.DB.Queryx
复制代码
  1. *sqlx.DB.QueryRowx
复制代码
  1. *sqlx.DB.Get
复制代码
  1. *sqlx.DB.Select
复制代码
,并且这些查询方法支持直接将查询结果扫描到结构体。
  1. sqlx
复制代码
为 SQL IN 操作提供了便捷方法
  1. sqlx.In
复制代码

为了使 SQL 更易阅读,
  1. sqlx
复制代码
提供了
  1. *sqlx.DB.NamedExec
复制代码
  1. *sqlx.DB.NamedQuery
复制代码
两个方法支持具名参数。
调用
  1. *sqlx.DB.Unsafe()
复制代码
方法能够获取
  1. unsafe
复制代码
属性为
  1. true
复制代码
  1. *sqlx.DB
复制代码
对象,在将查询结果扫描到结构体使可以用来忽略不匹配的记录字段。
除了能够将查询结果扫描到
  1. struct
复制代码
  1. sqlx
复制代码
还支持将查询结果扫描到
  1. map
复制代码
  1. slice
复制代码
  1. sqlx
复制代码
使用
  1. db
复制代码
结构体标签来映射查询字段和模型属性,如果不显式指定
  1. db
复制代码
标签,默认映射的模型属性为小写形式,可以通过
  1. *sqlx.DB.MapperFunc
复制代码
函数来修改默认行为。
本文完整代码示例我放在了 GitHub 上,欢迎点击查看。
以上就是Go语言使用sqlx操作数据库的示例详解的详细内容,更多关于Go sqlx操作数据库的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:24
  • 打卡月天数:0
  • 打卡总奖励:327
  • 最近打卡:2025-06-29 12:26:14
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
374
积分
58
注册时间
2023-1-1
最后登录
2025-6-29

发表于 2023-7-26 21:46:44 | 显示全部楼层
感谢大大分享~~~~~~~~
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
17
积分
14
注册时间
2022-12-30
最后登录
2022-12-30

发表于 2023-7-27 05:16:21 | 显示全部楼层
谢谢分享~~~~~
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
20
积分
20
注册时间
2022-12-24
最后登录
2022-12-24

发表于 2023-10-15 03:52:40 | 显示全部楼层
看看,学习学习~~~~
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
17
积分
14
注册时间
2022-12-24
最后登录
2022-12-24

发表于 2024-11-1 11:38:00 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
11
积分
2
注册时间
2023-12-7
最后登录
2023-12-7

发表于 2025-2-26 07:18:14 | 显示全部楼层
感谢楼主,顶。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-26
最后登录
2022-12-26

发表于 2025-3-10 00:43:12 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
4楼
5楼
6楼
7楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表