主頁 > 後端開發 > Go 語言之 sqlx 庫使用

Go 語言之 sqlx 庫使用

2023-06-14 08:14:39 後端開發

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

標籤:其他

上一篇:【python基礎】復雜資料型別-字典(嵌套)

下一篇:返回列表

標籤雲
其他(160901) Python(38226) JavaScript(25493) Java(18235) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7248) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4591) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1964) Web開發(1951) C++(1939) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Go 語言之 sqlx 庫使用

    # Go 語言之 sqlx 庫使用 ## 一、sqlx 庫安裝與連接 ### sqlx 介紹 sqlx is a library which provides a set of extensions on go's standard `database/sql` library. The sqlx ......

    uj5u.com 2023-06-14 08:14:39 more
  • 【python基礎】復雜資料型別-字典(嵌套)

    有時候,需要將一系列字典存盤在串列中,或將串列作為值存盤在字典中,這稱為**嵌套**。我們可以在串列中嵌套字典、在字典中嵌套串列、在字典中嵌套字典。 # 1.串列嵌套字典 我們可以把一個人的資訊放在字典中,但是多個人的資訊我們無法放在同一個字典中,所以就需要字典串列。 其語法格式: [字典1,字典2 ......

    uj5u.com 2023-06-14 08:09:16 more
  • 搭建springbootweb環境

    #搭建springboot環境(idea環境) 實作步驟: 1.基礎環境配置 2.maven配置 3.撰寫第一個程式helloworld(可能有兩個小問題) 4.運行(jar包運行,命令列運行) 一.基礎環境配置 進入idea,點擊file->new->project,在彈出的頁面上,選擇sprin ......

    uj5u.com 2023-06-14 07:58:44 more
  • 每天一道面試題:Spring的Bean生命周期

    Spring的Bean生命周期包括以下步驟: 1、實體化(Instantiation):當Spring容器接收到創建Bean的請求時,它會先實體化Bean物件。這個程序可以通過建構式、工廠方法或者反序列化等方式完成; 2、屬性賦值(Populate Properties):在實體化Bean物件后, ......

    uj5u.com 2023-06-14 07:53:30 more
  • Axure RP教程_編程入門自學教程_菜鳥教程-免費教程分享

    ## 教程簡介 Axure RP是一款專業的快速原型設計工具。Axure(發音:Ack-sure),代表美國Axure公司;RP則是Rapid Prototyping(快速原型)的縮寫。 Axure RP是美國Axure Software Solution公司旗艦產品,是一個專業的快速原型設計工具, ......

    uj5u.com 2023-06-14 07:53:25 more
  • celery筆記三之task和task的呼叫

    > 本文首發于公眾號:Hunter后端 > 原文鏈接:[celery筆記三之task和task的呼叫](https://mp.weixin.qq.com/s/AIobDZVDWV3r_XauvmkVKA) 這一篇筆記介紹 task 和 task 的呼叫。 以下是本篇筆記目錄: 1. 基礎的 task ......

    uj5u.com 2023-06-14 07:52:40 more
  • 爬取豆瓣Top250圖書資料

    #爬取豆瓣Top250圖書資料 專案的實作步驟 1.專案結構 2.獲取網頁資料 3.提取網頁中的關鍵資訊 4.保存資料 **1.專案結構** ![image](https://img2023.cnblogs.com/blog/3047082/202306/3047082-20230613170853 ......

    uj5u.com 2023-06-14 07:52:29 more
  • 【python基礎】復雜資料型別-字典(嵌套)

    有時候,需要將一系列字典存盤在串列中,或將串列作為值存盤在字典中,這稱為**嵌套**。我們可以在串列中嵌套字典、在字典中嵌套串列、在字典中嵌套字典。 # 1.串列嵌套字典 我們可以把一個人的資訊放在字典中,但是多個人的資訊我們無法放在同一個字典中,所以就需要字典串列。 其語法格式: [字典1,字典2 ......

    uj5u.com 2023-06-14 07:52:13 more
  • Python正則運算式完全指南

    **本篇文章將深入探討python的一項強大工具:正則運算式。正則運算式是一個強大的文本處理工具,可以用來匹配,搜索,替換和決議文本。我們將逐步展示如何在Python中使用正則運算式,包括其基本語法,常見用法和一些高級技巧。而在最后的“one more thing”部分,我們將探索一個不為人知但又非 ......

    uj5u.com 2023-06-14 07:52:02 more
  • PHP 合并圖片,微信小程式合并海報,合并文字

    //業務需求:我們需要一個微信小程式碼,但是是需要提供給別人掃碼的但是只有一個純粹的小程式碼是不好看的,所以需要推廣的海報圖片。再結合文字 最終效果 準備作業 1、需要海報的底圖 2、小程式碼的圖片 代碼部分結合YII2但不影響使用 完整程序 第一步:生成小程式碼圖片 第二步:縮放小程式碼的圖片大小 ......

    uj5u.com 2023-06-14 07:51:41 more