Go 語言之 sqlx 庫使用
一、sqlx 庫安裝與連接
sqlx 介紹
sqlx is a library which provides a set of extensions on go's standard database/sql
library. The sqlx versions of sql.DB
, sql.TX
, sql.Stmt
, et al. all leave the underlying interfaces untouched, so that their interfaces are a superset on the standard ones. This makes it relatively painless to integrate existing codebases using database/sql with sqlx.
Sqlx 是一個庫,它在 go 的標準資料庫/sql 庫上提供了一組擴展,
sqlx庫:https://github.com/jmoiron/sqlx
Illustrated guide to SQLX:http://jmoiron.github.io/sqlx/
sqlx:https://pkg.go.dev/github.com/jmoiron/sqlx
安裝
go get github.com/jmoiron/sqlx
創建 sqlx_demo 專案
https://img.uj5u.com/2023/06/14/354643140726511.png
創建 main.go 檔案
https://img.uj5u.com/2023/06/14/354643140726512.png
使用 go mod tidy 添加丟失的模塊和洗掉未使用的模塊
go mod tidy add missing and remove unused modules
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go mod tidy
go: finding module for package github.com/jmoiron/sqlx
go: finding module for package github.com/go-sql-driver/mysql
go: downloading github.com/jmoiron/sqlx v1.3.5
go: found github.com/go-sql-driver/mysql in github.com/go-sql-driver/mysql v1.7.1
go: found github.com/jmoiron/sqlx in github.com/jmoiron/sqlx v1.3.5
go: downloading github.com/lib/pq v1.2.0
go: downloading github.com/mattn/go-sqlite3 v1.14.6
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
連接 MySQL 資料庫
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
Code/go/sqlx_demo via ?? v1.20.3 via ?? base took 2.0s
?
二、sqlx 的 CRUD (查詢、插入、更新、修改)
查詢單條資料
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
// 1. 想要讓別的包能夠訪問到結構體中的欄位,那這個結構體中的欄位需要首字母大寫
// 2. Go 語言中引數的傳遞是值拷貝
// 查詢單條資料
func queryRowDemo(id int) (u user, err error) {
sqlStr := "SELECT id, name, age FROM user WHERE id=?"
// 使用這個資料庫,任何占位符引數都將被提供的引數替換,如果結果集為空,則回傳錯誤,
// 在 Get 中要修改傳入的變數,需要傳指標,即 &u
err = db.Get(&u, sqlStr, id)
if err != nil {
fmt.Printf("get failed, err:%v\n", err)
return u, err
}
return u, nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// 查詢
u, err := queryRowDemo(1)
if err != nil {
fmt.Printf("query row demo failed, err %v\n", err)
}
fmt.Printf("id: %d name: %s age: %d\n", u.ID, u.Name, u.Age)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
id: 1 name: 小喬 age: 16
Code/go/sqlx_demo via ?? v1.20.3 via ?? base took 3.1s
?
查詢多條資料
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
// 1. 想要讓別的包能夠訪問到結構體中的欄位,那這個結構體中的欄位需要首字母大寫
// 2. Go 語言中引數的傳遞是值拷貝
// 查詢多條資料
func queryMultiRowDemo(id int) (users []user, err error) {
sqlStr := "SELECT id, name, age FROM user WHERE id > ?"
err = db.Select(&users, sqlStr, id)
if err != nil {
fmt.Printf("query failed, err: %v\n", err)
return users, err
}
return users, nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// 查詢多條
users, err := queryMultiRowDemo(0)
if err != nil {
fmt.Printf("query many rows failed %v\n", err)
}
fmt.Printf("users: %#v\n", users)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base took 3.1s
? go run main.go
init DB succeeded
users: []main.user{main.user{ID:1, Age:16, Name:"小喬"}, main.user{ID:2, Age:22, Name:"小喬"}, main.user{ID:5, Age:100, Name:"昭君"}, main.user{ID:6, Age:16, Name:"黛玉"}}
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
~ via ?? base
? mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 476
Server version: 8.0.32 Homebrew
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use sql_test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 100 |
| 6 | 黛玉 | 16 |
+----+--------+------+
4 rows in set (0.00 sec)
mysql>
插入
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
// 1. 想要讓別的包能夠訪問到結構體中的欄位,那這個結構體中的欄位需要首字母大寫
// 2. Go 語言中引數的傳遞是值拷貝
// 插入資料
func insertRowDemo(name string, age int) (int64, error) {
sqlStr := "INSERT INTO user(name, age) VALUES (?,?)"
// 執行查詢而不回傳任何行,
// 引數用于查詢中的任何占位符引數
ret, err := db.Exec(sqlStr, name, age)
if err != nil {
fmt.Printf("insert failed, err: %v\n", err)
return 0, err
}
// 回傳資料庫回應命令生成的整數,
// 通常,當插入新行時,這將來自“自動增量”列,
// 并非所有資料庫都支持此特性,并且此類陳述句的語法各不相同,
var LastInsertId int64
LastInsertId, err = ret.LastInsertId() // 新插入資料的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err: %v\n", err)
return 0, err
}
return LastInsertId, nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// 插入資料
LastInsertId, err := insertRowDemo("寶玉", 17)
if err != nil {
fmt.Printf("insert row demo failed %v\n", err)
}
fmt.Printf("insert success, the id is %d.\n", LastInsertId)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
insert success, the id is 7.
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 100 |
| 6 | 黛玉 | 16 |
| 7 | 寶玉 | 17 |
+----+--------+------+
5 rows in set (0.01 sec)
mysql>
更新
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
// 1. 想要讓別的包能夠訪問到結構體中的欄位,那這個結構體中的欄位需要首字母大寫
// 2. Go 語言中引數的傳遞是值拷貝
// 更新資料
func updateRowDemo(age, id int) (int64, error) {
sqlStr := "UPDATE user SET age=? WHERE id = ?"
ret, err := db.Exec(sqlStr, age, id)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return 0, err
}
// 回傳受更新、插入或洗掉影響的行數,并非每個資料庫或資料庫驅動程式都支持此功能,
var n int64
n, err = ret.RowsAffected() // 操作影響的行數
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return 0, err
}
return n, nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// 更新資料
n, err := updateRowDemo(18, 5)
if err != nil {
fmt.Printf("update row demo failed %v\n", err)
}
fmt.Printf("update success, affected rows: %d\n", n)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
update success, affected rows: 1
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 18 |
| 6 | 黛玉 | 16 |
| 7 | 寶玉 | 17 |
+----+--------+------+
5 rows in set (0.00 sec)
mysql>
洗掉
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
// 1. 想要讓別的包能夠訪問到結構體中的欄位,那這個結構體中的欄位需要首字母大寫
// 2. Go 語言中引數的傳遞是值拷貝
// 洗掉資料
func deleteRowDemo(id int) (int64, error) {
sqlStr := "DELETE FROM user WHERE id = ?"
ret, err := db.Exec(sqlStr, id)
if err != nil {
fmt.Printf("delete failed, err:%v\n", err)
return 0, err
}
// 回傳受更新、插入或洗掉影響的行數,并非每個資料庫或資料庫驅動程式都支持此功能,
var n int64
n, err = ret.RowsAffected() // 操作影響的行數
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return 0, err
}
return n, nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// 洗掉資料
n, err := deleteRowDemo(7)
if err != nil {
fmt.Printf("delete row demo failed %v\n", err)
}
fmt.Printf("delete success, affected rows:%d\n", n)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
delete success, affected rows:1
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 18 |
| 6 | 黛玉 | 16 |
| 7 | 寶玉 | 17 |
+----+--------+------+
5 rows in set (0.00 sec)
mysql>
NamedExec 的使用
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
// 1. 想要讓別的包能夠訪問到結構體中的欄位,那這個結構體中的欄位需要首字母大寫
// 2. Go 語言中引數的傳遞是值拷貝
func insertUserDemo(arg interface{}) (int64, error) {
sqlStr := "INSERT INTO user (name,age) VALUES (:name,:age)"
// 使用這個資料庫,任何命名的占位符引數都將被arg中的欄位替換,
Result, err := db.NamedExec(sqlStr, arg)
if err != nil {
return 0, err
}
// 回傳資料庫回應命令生成的整數,
// 通常,當插入新行時,這將來自“自動增量”列,
// 并非所有資料庫都支持此特性,并且此類陳述句的語法各不相同,
var new_id int64
new_id, err = Result.LastInsertId() // 新插入資料的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err: %v\n", err)
return 0, err
}
return new_id, nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// NamedExec
arg := map[string]interface{}{"name": "李煜", "age": 26}
new_id, err := insertUserDemo(arg)
if err != nil {
fmt.Printf("insert user demo failed %v\n", err)
}
fmt.Printf("insert user success, the new id is %d\n", new_id)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
insert user success, the new id is 8
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 18 |
| 6 | 黛玉 | 16 |
| 8 | 李煜 | 26 |
+----+--------+------+
5 rows in set (0.01 sec)
mysql>
NamedQuery 的使用
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
func namedQueryMap(arg interface{}) {
sqlStr := "SELECT * FROM user WHERE name=:name"
// 任何命名的占位符引數都將被arg中的欄位替換,
rows, err := db.NamedQuery(sqlStr, arg)
if err != nil {
fmt.Printf("db.NamedQuery failed, err:%v\n", err)
return
}
defer rows.Close()
for rows.Next() {
results := make(map[string]interface{})
// 使用 map 做命名查詢
err := rows.MapScan(results)
//dest, err := rows.SliceScan()
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
continue
}
// 將 "name" 欄位的值轉換為字串型別
if nameBytes, ok := results["name"].([]uint8); ok {
results["name"] = string(nameBytes)
}
fmt.Printf("NamedQuery Map user: %#v\n", results)
}
}
func namedQuerySlice(arg interface{}) {
sqlStr := "SELECT * FROM user WHERE name=:name"
// 任何命名的占位符引數都將被arg中的欄位替換,
rows, err := db.NamedQuery(sqlStr, arg)
if err != nil {
fmt.Printf("db.NamedQuery failed, err:%v\n", err)
return
}
defer rows.Close()
for rows.Next() {
results, err := rows.SliceScan()
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
continue
}
if len(results) >= 3 {
id, _ := results[0].(int64)
name, _ := results[1].([]uint8)
age, _ := results[2].(int64)
fmt.Printf("NamedQuery Slice user: id=%d, name=%s, age=%d\n", id, string(name), age)
}
}
}
func namedQueryStruct(arg interface{}) {
sqlStr := "SELECT * FROM user WHERE name=:name"
// 任何命名的占位符引數都將被arg中的欄位替換,
rows, err := db.NamedQuery(sqlStr, arg)
if err != nil {
fmt.Printf("db.NamedQuery failed, err:%v\n", err)
return
}
defer rows.Close()
for rows.Next() {
var results user
err := rows.StructScan(&results)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
continue
}
fmt.Printf("NamedQuery struct user: %#v\n", results)
}
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// NamedQuery
// 使用 map 做命名查詢
arg := map[string]interface{}{"name": "黛玉"}
namedQueryMap(arg)
// 使用結構體命名查詢,根據結構體欄位的 db tag進行映射
arg1 := user{Name: "黛玉"}
namedQueryStruct(arg1)
// 使用 Slice 做命名查詢
arg2 := []user{arg1}
namedQuerySlice(arg2)
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
NamedQuery Map user: map[string]interface {}{"age":16, "id":6, "name":"黛玉"}
NamedQuery struct user: main.user{ID:6, Age:16, Name:"黛玉"}
NamedQuery Slice user: id=6, name=黛玉, age=16
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 18 |
| 6 | 黛玉 | 16 |
| 8 | 李煜 | 26 |
+----+--------+------+
5 rows in set (0.00 sec)
mysql>
sqlx 事務
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 連接到資料庫并使用ping進行驗證,
// 也可以使用 MustConnect MustConnect連接到資料庫,并在出現錯誤時恐慌 panic,
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20) // 設定資料庫的最大打開連接數,
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數,
return
}
type user struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
func executeQuery(tx *sqlx.Tx, sqlStr string, args ...interface{}) error {
rs, err := tx.Exec(sqlStr, args...)
if err != nil {
return err
}
// 回傳受更新、插入或洗掉影響的行數,并非每個資料庫或資料庫驅動程式都支持此功能,
var n int64
n, err = rs.RowsAffected()
if err != nil {
return err
}
if n != 1 { // 如果受影響的行數不是 1 ,說明更新出了問題,return 錯誤
return fmt.Errorf("exec failed for id: %d", args[1])
}
return nil
}
func transactionDemo() error {
// 開始事務并回傳sqlx.Tx而不是sql.Tx,
tx, err := db.Beginx() // 開啟事務
if err != nil {
return fmt.Errorf("begin trans failed, err: %v", err)
}
defer func() {
// recover 恢復內置功能允許程式管理一個恐慌的程式的行為,
// recover的回傳值報告了例程是否處于恐慌狀態,
// recover 捕獲當前函式可能會出現的 panic,執行恢復操作,即先回滾然后 panic
if p := recover(); p != nil {
_ = tx.Rollback() // 中止回滾事務,
panic(p)
} else if err != nil { // 判斷 err 是否為空,如果當前函式有錯誤,則回滾
fmt.Printf("begin trans failed, Rollback err: %v\n", err)
_ = tx.Rollback()
} else { // 如果沒有錯誤也沒有 panic 則提交
err = tx.Commit()
fmt.Printf("error committing transaction: %v\n", err)
}
}()
sqlStr := "UPDATE user SET age=? WHERE id=?"
queries := []struct {
id int
age int
}{
{1, 88},
{3, 16},
}
// 更新用戶表 user 中兩個用戶的年齡,只有當這兩個用戶的年齡都更新成功的情況下才會去提交
for _, query := range queries {
if err = executeQuery(tx, sqlStr, query.age, query.id); err != nil {
return err
}
}
return nil
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB succeeded")
// 事務
_ = transactionDemo()
}
運行
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
? go run main.go
init DB succeeded
begin trans failed, Rollback err: exec failed for id: 3
Code/go/sqlx_demo via ?? v1.20.3 via ?? base
?
SQL 查詢結果
mysql> select * from user; # 事務執行前
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 12 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 18 |
| 6 | 黛玉 | 16 |
| 8 | 李煜 | 26 |
+----+--------+------+
5 rows in set (0.00 sec)
mysql> select * from user; # 事務執行后
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 12 |
| 2 | 小喬 | 22 |
| 5 | 昭君 | 18 |
| 6 | 黛玉 | 16 |
| 8 | 李煜 | 26 |
+----+--------+------+
5 rows in set (0.01 sec)
mysql>
- 使用 defer 來做事務最終是回滾還是提交的判斷,
- 執行之后,事務回滾了,查詢資料庫未發生改變,是因為 ID 為 3 的那條記錄不存在,
本文來自博客園,作者:尋月隱君,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17477516.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555068.html
標籤:其他
下一篇:返回列表