亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go語言使用sqlx操作數(shù)據(jù)庫的示例詳解

 更新時間:2023年06月19日 09:57:52   作者:江湖十年  
sqlx?是?Go?語言中一個流行的第三方包,它提供了對?Go?標(biāo)準(zhǔn)庫?database/sql?的擴(kuò)展,本文重點講解?sqlx?在?database/sql?基礎(chǔ)上擴(kuò)展的功能,希望對大家有所幫助

sqlx 是 Go 語言中一個流行的第三方包,它提供了對 Go 標(biāo)準(zhǔn)庫 database/sql 的擴(kuò)展,旨在簡化和改進(jìn) Go 語言中使用 SQL 的體驗,并提供了更加強大的數(shù)據(jù)庫交互功能。sqlx 保留了 database/sql 接口不變,是 database/sql 的超集,這使得將現(xiàn)有項目中使用的 database/sql 替換為 sqlx 變得相當(dāng)輕松。

本文重點講解 sqlxdatabase/sql 基礎(chǔ)上擴(kuò)展的功能,對于 database/sql 已經(jīng)支持的功能則不會詳細(xì)講解。如果你對 database/sql 不熟悉,可以查看我的另一篇文章《在 Go 中如何使用 database/sql 來操作數(shù)據(jù)庫》。

安裝

sqlx 安裝方式同 Go 語言中其他第三方包一樣:

$ go get github.com/jmoiron/sqlx

sqlx 類型設(shè)計

sqlx 的設(shè)計與 database/sql 差別不大,編碼風(fēng)格較為統(tǒng)一,參考 database/sql 標(biāo)準(zhǔn)庫,sqlx 提供了如下幾種與之對應(yīng)的數(shù)據(jù)類型:

  • sqlx.DB:類似于 sql.DB,表示數(shù)據(jù)庫對象,可以用來操作數(shù)據(jù)庫。
  • sqlx.Tx:類似于 sql.Tx,事務(wù)對象。
  • sqlx.Stmt:類似于 sql.Stmt,預(yù)處理 SQL 語句。
  • sqlx.NamedStmt:對 sqlx.Stmt 的封裝,支持具名參數(shù)。
  • sqlx.Rows:類似于 sql.Rowssqlx.Queryx 的返回結(jié)果。
  • sqlx.Row:類似于 sql.Rowsqlx.QueryRowx 的返回結(jié)果。

以上類型與 database/sql 提供的對應(yīng)類型在功能上區(qū)別不大,但 sqlx 為這些類型提供了更友好的方法。

準(zhǔn)備

為了演示 sqlx 用法,我準(zhǔn)備了如下 MySQL 數(shù)據(jù)庫表:

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 命令行或圖形化工具創(chuàng)建這張表。

連接數(shù)據(jù)庫

使用 sqlx 連接數(shù)據(jù)庫:

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 連接數(shù)據(jù)庫
	db, err = sqlx.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}
	// 2. 使用 sqlx.Open 變體方法 sqlx.MustOpen 連接數(shù)據(jù)庫,如果出現(xiàn)錯誤直接 panic
	db = sqlx.MustOpen("mysql", dsn)
	// 3. 如果已經(jīng)有了 *sql.DB 對象,則可以使用 sqlx.NewDb 連接數(shù)據(jù)庫,得到 *sqlx.DB 對象
	sqlDB, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}
	db = sqlx.NewDb(sqlDB, "mysql")
	// 4. 使用 sqlx.Connect 連接數(shù)據(jù)庫,等價于 sqlx.Open + db.Ping
	db, err = sqlx.Connect("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}
	// 5. 使用 sqlx.Connect 變體方法 sqlx.MustConnect 連接數(shù)據(jù)庫,如果出現(xiàn)錯誤直接 panic
	db = sqlx.MustConnect("mysql", dsn)
}

sqlx 中我們可以通過以上 5 種方式連接數(shù)據(jù)庫。

sqlx.Open 對標(biāo) sql.Open 方法,返回 *sqlx.DB 類型。

sqlx.MustOpensqlx.Open 一樣會返回 *sqlx.DB 實例,但如果遇到錯誤則會 panic。

sqlx.NewDb 支持從一個 database/sql 包的 *sql.DB 對象創(chuàng)建一個新的 *sqlx.DB 類型,并且需要指定驅(qū)動名稱。

使用前 3 種方式連接數(shù)據(jù)庫并不會立即與數(shù)據(jù)庫建立連接,連接將會在合適的時候延遲建立。為了確保能夠正常連接數(shù)據(jù)庫,往往需要調(diào)用 db.Ping() 方法進(jìn)行驗證:

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 方法的基礎(chǔ)上,提供了遇到錯誤立即 panic 的功能。看到 sqlx.MustConnect 方法的定義你就明白了:

func MustConnect(driverName, dataSourceName string) *DB {
	db, err := Connect(driverName, dataSourceName)
	if err != nil {
		panic(err)
	}
	return db
}

以后當(dāng)你遇見 MustXxx 類似方法名時就應(yīng)該想到,其功能往往等價于 Xxx 方法,不過在其內(nèi)部實現(xiàn)中,遇到 error 不再返回,而是直接進(jìn)行 panic,這也是 Go 語言很多庫中的慣用方法。

聲明模型

我們定義一個 User 結(jié)構(gòu)體來映射數(shù)據(jù)庫中的 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 結(jié)構(gòu)體在這里可以被稱為「模型」。

執(zhí)行 SQL 命令

database/sql 包提供了 *sql.DB.Exec 方法來執(zhí)行一條 SQL 命令,sqlx 對其進(jìn)行了擴(kuò)展,提供了 *sqlx.DB.MustExec 方法來執(zhí)行一條 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,其內(nèi)部調(diào)用的是 *sqlx.DB.Exec 方法。

執(zhí)行 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 在這兩個方法的基礎(chǔ)上,擴(kuò)展出如下兩個方法:

func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryRowx(query string, args ...interface{}) *Row

這兩個方法返回的類型正是前文 sqlx 類型設(shè)計 中提到的 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 提供了便捷方法可以將查詢結(jié)果直接掃描到結(jié)構(gòu)體
		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 得到了擴(kuò)展,其提供的 StructScan 方法能夠方便的將查詢結(jié)果直接掃描到 User 結(jié)構(gòu)體,這極大的增加了便攜性,我們再也不用像使用 *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 方法將查詢結(jié)果掃描到結(jié)構(gòu)體。

另外,這里使用了鏈?zhǔn)秸{(diào)用的方式,在調(diào)用 db.QueryRowx() 之后直接調(diào)用了 .StructScan(&u),接收的 errStructScan 的返回結(jié)果。這是因為 db.QueryRowx() 的返回結(jié)果 *sqlx.Row 中記錄了錯誤信息 err,如果查詢階段遇到錯誤會被記錄到 *sqlx.Row.err 中。在調(diào)用 StructScan 方法階段,其內(nèi)部首先判斷 r.err != nil,如果存在 err 直接返回錯誤,沒有錯誤則將查詢結(jié)果掃描到 dest 參數(shù)接收到的結(jié)構(gòu)體指針,代碼實現(xiàn)如下:

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 不僅擴(kuò)展了 *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
	// 查詢記錄掃描數(shù)據(jù)到 struct
	err := db.Get(&u, "SELECT * FROM user WHERE id = ?", id)
	return u, err
}

可以發(fā)現(xiàn) *sqlx.DB.Get 方法用起來非常簡單,我們不再需要調(diào)用 StructScan 方法將查詢結(jié)果掃描到結(jié)構(gòu)體中,只需要將結(jié)構(gòu)體指針當(dāng)作 Get 方法的第一個參數(shù)傳遞進(jìn)去即可。

其代碼實現(xiàn)如下:

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

根據(jù)源碼可以看出,*sqlx.DB.Get 內(nèi)部調(diào)用了 *sqlx.DB.QueryRowx 方法。

Select

使用 *sqlx.DB.Select 方法查詢記錄如下:

func SelectUsers(db *sqlx.DB) ([]User, error) {
	var us []User
	// 查詢記錄掃描數(shù)據(jù)到 slice
	err := db.Select(&us, "SELECT * FROM user")
	return us, err
}

可以發(fā)現(xiàn) *sqlx.DB.Select 方法用起來同樣非常簡單,它可以直接將查詢結(jié)果掃描到 []User 切片中。

其代碼實現(xiàn)如下:

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

根據(jù)源碼可以看出,*sqlx.DB.Select 內(nèi)部調(diào)用了 *sqlx.DB.Queryx 方法。

sqlx.In

database/sql 中如果想要執(zhí)行 SQL IN 查詢,由于 IN 查詢參數(shù)長度不固定,我們不得不使用 fmt.Sprintf 來動態(tài)拼接 SQL 語句,以保證 SQL 中參數(shù)占位符的個數(shù)是正確的。

sqlx 提供了 In 方法來支持 SQL IN 查詢,這極大的簡化了代碼,也使得代碼更易維護(hù)和安全。

使用示例如下:

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
}

調(diào)用 sqlx.In 并傳遞 SQL 語句以及切片類型的參數(shù),它將返回新的查詢 SQL query 以及參數(shù) args,這個 query 將會根據(jù) ids 來動態(tài)調(diào)整。

比如我們傳遞 ids[]int64{1, 2, 3},則得到 querySELECT * FROM user WHERE id IN (?, ?, ?)。

注意,我們接下來又調(diào)用 db.Rebind(query) 重新綁定了 query 變量的參數(shù)占位符。如果你使用 MySQL 數(shù)據(jù)庫,這不是必須的,因為我們使用的 MySQL 驅(qū)動程序參數(shù)占位符就是 ?。而如果你使用 PostgreSQL 數(shù)據(jù)庫,由于 PostgreSQL 驅(qū)動程序參數(shù)占位符是 $n,這時就必須要調(diào)用 db.Rebind(query) 方法來轉(zhuǎn)換參數(shù)占位符了。

它會將 SELECT * FROM user WHERE id IN (?, ?, ?) 中的參數(shù)占位符轉(zhuǎn)換為 PostgreSQL 驅(qū)動程序能夠識別的參數(shù)占位符 SELECT * FROM user WHERE id IN ($1, $2, $3)

之后的代碼就跟使用 database/sql 查詢記錄沒什么兩樣了。

使用具名參數(shù)

sqlx 提供了兩個方法 NamedExec、NamedQuery,它們能夠支持具名參數(shù) :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 的方式來命名參數(shù),它能夠匹配 mapstruct 對應(yīng)字段的參數(shù)值,這樣的 SQL 語句可讀性更強。

事務(wù)

在事務(wù)的支持上,sqlx 擴(kuò)展出了 Must 版本的事務(wù),使用示例如下:

func MustTransaction(db *sqlx.DB) error {
	tx := db.MustBegin()
	tx.MustExec("UPDATE user SET age = 25 WHERE id = ?", 1)
	return tx.Commit()
}

不過這種用法不多,你知道就行。以下是事務(wù)的推薦用法:

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 語句來處理事務(wù)的回滾操作,這樣就不必在每次處理錯誤時重復(fù)的編寫調(diào)用 tx.Rollback() 的代碼。

如果代碼正常執(zhí)行到最后,通過 tx.Commit() 來提交事務(wù),此時即使再調(diào)用 tx.Rollback() 也不會對結(jié)果產(chǎn)生影響。

預(yù)處理語句

sqlx 針對 *sql.DB.Prepare 擴(kuò)展出了 *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 內(nèi)部還是調(diào)用的 *sql.DB.Preapre 方法,只不過將其返回結(jié)果構(gòu)造成 *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 語句中查詢了 idname、email、age 4 個字段,而 user 結(jié)構(gòu)體則只有 ID、NameEmail 3 個屬性,由于無法一一對應(yīng),執(zhí)行以上代碼,我們將得到如下報錯信息:

missing destination name age in *struct { ID int; Name string; Email string }

這種表現(xiàn)是合理的,符合 Go 語言的編程風(fēng)格,盡早暴露錯誤有助于減少代碼存在 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 對象,然后再調(diào)用它的 Get 方法。

*sqlx.DB 定義如下:

type DB struct {
	*sql.DB
	driverName string
	unsafe     bool
	Mapper     *reflectx.Mapper
}

當(dāng) unsafe 屬性為 true 時,*sqlx.DB 對象會忽略不匹配的字段,使代碼能夠正常運行,并將能夠匹配的字段正確綁定到 user 結(jié)構(gòu)體對象上。

通過這個屬性的名稱我們就知道,這是不安全的做法,不被推薦。

與未使用的變量一樣,被忽略的列是對網(wǎng)絡(luò)和數(shù)據(jù)庫資源的浪費,并且這很容易導(dǎo)致出現(xiàn)模型與數(shù)據(jù)庫表不匹配而不被感知的情況。

Scan 變體

前文示例中,我們見過了 *sqlx.Rows.Scan 的變體 *sqlx.Rows.StructScan 的用法,它能夠方便的將查詢結(jié)果掃描到 struct 中。

sqlx 還提供了 *sqlx.Rows.MapScan、*sqlx.Rows.SliceScan 兩個方法,能夠?qū)⒉樵兘Y(jié)果分別掃描到 mapslice 中。

使用示例如下:

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) 用法類似,都是將接收查詢結(jié)果集的目標(biāo)模型指針變量當(dāng)作參數(shù)傳遞進(jìn)來。

rows.SliceScan() 用法略有不同,它不接收參數(shù),而是將結(jié)果保存在 []interface{} 中并返回。

可以按需使用以上兩個方法。

控制字段名稱映射

講到這里,想必不少同學(xué)心里可能存在一個疑惑,rows.StructScan(&u) 在將查詢記錄的字段映射到對應(yīng)結(jié)構(gòu)體屬性時,是如何找到對應(yīng)關(guān)系的呢?

答案就是 db 結(jié)構(gòu)體標(biāo)簽。

回顧前文講 聲明模型 時,User 結(jié)構(gòu)體中定義的 CreatedAtUpdatedAt 兩個字段,定義如下:

CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`

這里顯式的標(biāo)明了結(jié)構(gòu)體標(biāo)簽 db,sqlx 正是使用 db 標(biāo)簽來映射查詢字段和模型屬性。

默認(rèn)情況下,結(jié)構(gòu)體字段會被映射成全小寫形式,如 ID 字段會被映射為 id,而 CreatedAt 字段會被映射為 createdat

因為在 user 數(shù)據(jù)庫表中,創(chuàng)建時間和更新時間兩個字段分別為 created_atupdated_at,與 sqlx 默認(rèn)字段映射規(guī)則不匹配,所以我才顯式的為 CreatedAtUpdatedAt 兩個字段指明了 db 標(biāo)簽,這樣 sqlxrows.StructScan 就能正常工作了。

當(dāng)然,數(shù)據(jù)庫字段不一定都是小寫,如果你的數(shù)據(jù)庫字段為全大寫,sqlx 提供了 *sqlx.DB.MapperFunc 方法來控制查詢字段和模型屬性的映射關(guān)系。

其使用示例如下:

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 對象,我們復(fù)制了一個 copyDB,調(diào)用 copyDB.MapperFunc 并將 strings.ToUpper 傳遞進(jìn)來。

注意這里的查詢語句中,查詢字段全部通過 as 重新命名成了大寫形式,而 User 模型字段 db 默認(rèn)都為小寫形式。

copyDB.MapperFunc(strings.ToUpper) 的作用,就是在調(diào)用 Get 方法將查詢結(jié)果掃描到結(jié)構(gòu)體時,把 User 模型的小寫字段,通過 strings.ToUpper 方法轉(zhuǎn)成大寫,這樣查詢字段和模型屬性就全為大寫了,也就能夠一一匹配上了。

還有一種情況,如果你的模型已存在 json 標(biāo)簽,并且不想重復(fù)的再抄一遍到 db 標(biāo)簽,我們可以直接使用 json 標(biāo)簽來映射查詢字段和模型屬性。

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) 將模型映射的標(biāo)簽由 db 改為 json,并通過 strings.ToLower 方法轉(zhuǎn)換為小寫。

reflectx 按照如下方式導(dǎo)入:

import "github.com/jmoiron/sqlx/reflectx"

現(xiàn)在,查詢語句中 name 屬性通過使用 as 被重命名為 username,而 username 剛好與 User 模型中 Name 字段的 json 標(biāo)簽相對應(yīng):

Name     sql.NullString `json:"username"`

所以,以上示例代碼能夠正確映射查詢字段和模型屬性。

總結(jié)

sqlx 建立在 database/sql 包之上,用于簡化和增強與關(guān)系型數(shù)據(jù)庫的交互操作。

對常見數(shù)據(jù)庫操作方法,sqlx 提供了 Must 版本,如 sqlx.MustOpen 用來連接數(shù)據(jù)庫,*sqlx.DB.MustExec 用來執(zhí)行 SQL 語句,當(dāng)遇到 error 時將會直接 panic

sqlx 還擴(kuò)展了查詢方法 *sqlx.DB.Queryx、*sqlx.DB.QueryRowx、*sqlx.DB.Get*sqlx.DB.Select,并且這些查詢方法支持直接將查詢結(jié)果掃描到結(jié)構(gòu)體。

sqlx 為 SQL IN 操作提供了便捷方法 sqlx.In

為了使 SQL 更易閱讀,sqlx 提供了 *sqlx.DB.NamedExec*sqlx.DB.NamedQuery 兩個方法支持具名參數(shù)。

調(diào)用 *sqlx.DB.Unsafe() 方法能夠獲取 unsafe 屬性為 true*sqlx.DB 對象,在將查詢結(jié)果掃描到結(jié)構(gòu)體使可以用來忽略不匹配的記錄字段。

除了能夠?qū)⒉樵兘Y(jié)果掃描到 struct,sqlx 還支持將查詢結(jié)果掃描到 mapslice

sqlx 使用 db 結(jié)構(gòu)體標(biāo)簽來映射查詢字段和模型屬性,如果不顯式指定 db 標(biāo)簽,默認(rèn)映射的模型屬性為小寫形式,可以通過 *sqlx.DB.MapperFunc 函數(shù)來修改默認(rèn)行為。

本文完整代碼示例我放在了 GitHub 上,歡迎點擊查看。

以上就是Go語言使用sqlx操作數(shù)據(jù)庫的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go sqlx操作數(shù)據(jù)庫的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang Copier入門到入坑探究

    Golang Copier入門到入坑探究

    這篇文章主要為大家介紹了Golang Copier入門到入坑探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • golang將切片或數(shù)組根據(jù)某個字段進(jìn)行分組操作

    golang將切片或數(shù)組根據(jù)某個字段進(jìn)行分組操作

    這篇文章主要介紹了golang將切片或數(shù)組根據(jù)某個字段進(jìn)行分組操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang開發(fā)之接口的具體使用詳解

    Golang開發(fā)之接口的具體使用詳解

    在 Golang 中,接口是一種類型,它是由一組方法簽名組成的抽象集合。這篇文章主要為大家介紹了Golang接口的具體使用,希望對大家有所幫助
    2023-04-04
  • go語言中布隆過濾器低空間成本判斷元素是否存在方式

    go語言中布隆過濾器低空間成本判斷元素是否存在方式

    這篇文章主要為大家介紹了go語言中布隆過濾器低空間成本判斷元素是否存在方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 一文帶你輕松理解Go中的內(nèi)存逃逸問題

    一文帶你輕松理解Go中的內(nèi)存逃逸問題

    這篇文章主要給大家介紹Go中的內(nèi)存逃逸問題,文中通過代碼示例講解的非常詳細(xì),對我們的學(xué)習(xí)或工作有一定的參考價值,感興趣的同學(xué)可以跟著小編一起來學(xué)習(xí)
    2023-06-06
  • Go實現(xiàn)map并發(fā)安全的3種方式總結(jié)

    Go實現(xiàn)map并發(fā)安全的3種方式總結(jié)

    Go的原生map不是并發(fā)安全的,在多協(xié)程讀寫同一個map的時候,安全性無法得到保障,這篇文章主要給大家總結(jié)介紹了關(guān)于Go實現(xiàn)map并發(fā)安全的3種方式,需要的朋友可以參考下
    2023-10-10
  • 詳解go語言json的使用技巧

    詳解go語言json的使用技巧

    這篇文章主要介紹了詳解go語言json的使用技巧,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • 15個Golang中時間處理的實用函數(shù)

    15個Golang中時間處理的實用函數(shù)

    在Go編程中,處理日期和時間是一項常見任務(wù),涉及到精確性和靈活性,本文將介紹一系列實用函數(shù),它們充當(dāng)time包的包裝器,需要的可以參考下
    2024-01-01
  • Go語言開源庫實現(xiàn)Onvif協(xié)議客戶端設(shè)備搜索

    Go語言開源庫實現(xiàn)Onvif協(xié)議客戶端設(shè)備搜索

    這篇文章主要為大家介紹了Go語言O(shè)nvif協(xié)議客戶端設(shè)備搜索示例實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • Go語言struct類型詳解

    Go語言struct類型詳解

    這篇文章主要介紹了Go語言struct類型詳解,struct是一種數(shù)據(jù)類型,可以用來定義自己想的數(shù)據(jù)類型,需要的朋友可以參考下
    2014-10-10

最新評論