Go Web 编程
SQL 科普
bindvars 绑定变量
在 MySQL 语句中经常会出现占位符 ?
, 在 SQL
内部称为 bindvars
查询占位符。
我们在日常的 SQL
语句中 任何时候都不应该自己拼接SQL
语句, 应该始终使用 bindvars
占位符 来对数据发送值, 它可以防止SQL
注入攻击。
在Go语言的 database/sql
中, 不会尝试对查询文本进行任何校验, 它与编码参数一起按照原样发送到服务器。
不同的数据库有不同的占位符
注意:
bindvars
仅用于参数化, 不允许更改 SQL
语句的结构。
bindvars
占位符不能用于插入 表名。 如: select * from ?
这是不允许的
bindvars
占位符不能用于插入 列名。如: select ?,? from user
这也是不允许的
sql 标准库
下载 Mysql 驱动
1
2
3
4
|
go get -u github.com/go-sql-driver/mysql
|
驱动说明
1
2
3
4
5
| // sql.Open 函数
func Open(driverName, dataSourceName string) (*DB, error)
|
driverName
- 指具体的数据库类型, 如: mysql
dataSourceName
- 指具体的数据信息, 如: 账号、密码、协议、IP、端口、数据库名 等。
连接数据库例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package main
import (
"database/sql"
// 引入 mysql 驱动, 只需要用到 init() 方法
_ "github.com/go-sql-driver/mysql"
)
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
// 关闭数据库连接
defer db.Close()
// Ping 函数 尝试校验 dsn
err = DB.Ping()
if err != nil {
return err
}
}
|
初始化连接的例子
sql.Open
返回的 DB 对象可以安全的被多个goroutine
并发使用, 并维护自己的空闲连接池, 因此很少需要关闭 DB 这个对象。
SetMaxOpenConns
设置与数据库建立连接的最大连接数。默认为0 (无限制)
SetMaxIdleConns
设置连接池中最大闲置连接数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| package main
import (
"database/sql"
"fmt"
// 引入 mysql 驱动, 只需要用到 init() 方法
_ "github.com/go-sql-driver/mysql"
)
// 定义全局的 数据库对象 DB
var DB *sql.DB
func initDB() (err error) {
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True"
// 这里 DB 赋值是给 上面定义的全局变量赋值, 不要使用 :=
DB, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
// 调用 Ping() 方法会尝试连接数据库,校检 dsn 是否正确
err = DB.Ping()
if err != nil {
return err
}
// 最大连接数
DB.SetMaxOpenConns(100)
// 连接池中空闲连接数
DB.SetMaxIdleConns(50)
return nil
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
}
|
增删改查操作
查询操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
type user struct {
id int
name string
age int
}
func queryRowToID(id int) {
var u user
sqlStr := "select id, name, age from user where id=?"
// .Scan 是链式操作, Scan是将查询出来的结果赋值到结构体对应的字段中.
// 使用 QueryRow 以后必须要调用 Scan 方法,否则数据库连接不会被释放.
err := DB.QueryRow(sqlStr, id).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("QueryRow Scan Failed, Error %v\n", err)
return
}
fmt.Printf("ID: %d Name: %s Age: %d \n", u.id, u.name, u.age)
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
queryRowToID(1)
}
|
1
2
| # 输出结果
ID: 1 Name: jicki Age: 20
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
type user struct {
id int
name string
age int
}
func queryMultiRowToID(id int) {
var u user
sqlStr := "select id, name, age from user where id > ?"
rows, err := DB.Query(sqlStr, id)
if err != nil {
fmt.Printf("QueryMulti Failed, Error %v\n", err)
return
}
// 关闭数据库连接
defer rows.Close()
// 遍历读取的多条数据
for rows.Next() {
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("QueryMulti Scan Failed, Error %v\n", err)
return
}
fmt.Printf("ID: %d Name: %s Age: %d \n", u.id, u.name, u.age)
}
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
queryMultiRowToID(0)
}
|
1
2
3
4
| # 输出结果
ID: 1 Name: jicki Age: 20
ID: 2 Name: Jack Age: 30
ID: 3 Name: Vicki Age: 30
|
插入数据操作
插入、更新、删除操作都可以使用Exec
方法。
Exec
执行一次命令(包含查询、删除、更新、插入等), 返回的Result
是对已执行的SQL命令的总结。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
func insertRow(name string, age int) {
sqlStr := "insert into user(name,age) values (?,?)"
ret, err := DB.Exec(sqlStr, name, age)
if err != nil {
fmt.Printf("Insert Row Error %v\n", err)
return
}
var inID int64
// 获取Last最后一次插入数据的ID
inID, err = ret.LastInsertId()
if err != nil {
fmt.Printf("Get LastInsertId Error %v\n", err)
return
}
fmt.Printf("Insert Success ID %d \n", inID)
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
insertRow("小炒肉", 10)
queryMultiRowToID(0)
}
|
1
2
3
4
5
6
7
| # 输出结果
Insert Success ID 4
ID: 1 Name: jicki Age: 20
ID: 2 Name: Jack Age: 30
ID: 3 Name: Vicki Age: 30
ID: 4 Name: 小炒肉 Age: 10
|
更新数据操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
func updateRowToAge(age, id int) {
sqlStr := "update user set age = ? where id = ?"
ret, err := DB.Exec(sqlStr, age, id)
if err != nil {
fmt.Printf("Update Failed Error %v\n", err)
return
}
var n int64
// RowsAffected 返回更新影响的行数
n, err = ret.RowsAffected()
if err != nil {
fmt.Printf("Get RowAffected Failed Error %v\n", err)
return
}
fmt.Printf("Update Success Affected Row: %d \n", n)
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
queryRowToID(1)
updateRowToAge(11, 1)
queryRowToID(1)
}
|
1
2
3
4
5
6
| # 输出结果
ID: 1 Name: jicki Age: 20
Update Success Affected Row: 1
ID: 1 Name: jicki Age: 11
|
删除数据操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
func deleteRowToID(id int) {
sqlStr := "delete from user where id = ?"
ret, err := DB.Exec(sqlStr, id)
if err != nil {
fmt.Printf("Delete Failed Error %v\n", err)
return
}
var n int64
// RowsAffected 返回更新影响的行数
n, err = ret.RowsAffected()
if err != nil {
fmt.Printf("Get RowAffected Failed Error %v\n", err)
return
}
fmt.Printf("Delete Success Affected Row: %d \n", n)
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
queryMultiRowToID(0)
deleteRowToID(3)
queryMultiRowToID(0)
}
|
1
2
3
4
5
6
7
8
9
10
| # 输出结果
ID: 1 Name: jicki Age: 11
ID: 2 Name: Jack Age: 30
ID: 3 Name: Vicki Age: 30
ID: 4 Name: 小炒肉 Age: 10
Delete Success Affected Row: 1
ID: 1 Name: jicki Age: 11
ID: 2 Name: Jack Age: 30
ID: 4 Name: 小炒肉 Age: 10
|
Mysql 预处理
预处理
普通SQL
语句执行过程:
- 客户端对
SQL
语句进行占位符替换得到完整的SQL
语句。 - 客户端发送完整
SQL
语句到 MySQL 服务端。 - MySQL 服务端执行完整的
SQL
语句并将结果返回给客户端。
预处理执行过程:
- 把
SQL
语句分成两部分, 命令部分与数据部分。 - 把命令部分发送给 MySQL 服务端, MySQL 服务端进行
SQL
预处理。 - 数据部分发送给 MySQL 服务端, MySQL 服务端对
SQL
语句进行占位符替换。 - MySQL 服务端执行完整的
SQL
语句并将结果返回给客户端。
预处理的优点:
Go 实现MySQL 预处理
database/sql
包中使用 Prepare
方法实现预处理操作。
func (db *DB) Prepare(query string) (*Stmt, error)
Prepare
方法会先将 SQL
语句发送给 MySQL 服务端, 服务端返回一个状态用于后续的查询和操作。返回值可以同时执行多个查询和操作。
预处理 查询例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
type user struct {
id int
name string
age int
}
func perPareQuery(id int) {
sqlStr := "select id, name, age from user where id > ?"
stmt, err := DB.Prepare(sqlStr)
if err != nil {
fmt.Printf("Prepare Failed Error %v\n", err)
return
}
defer stmt.Close()
rows, err := stmt.Query(id)
if err != nil {
fmt.Printf("Prepare Query Error %v\n", err)
return
}
defer rows.Close()
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("Perpare Scan Failed Error %v\n", err)
return
}
fmt.Printf("ID: %d Name: %s Age: %d \n", u.id, u.name, u.age)
}
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
perPareQuery(0)
}
|
1
2
3
4
| # 输出结果
ID: 1 Name: jicki Age: 11
ID: 2 Name: Jack Age: 30
ID: 4 Name: 小炒肉 Age: 10
|
预处理 插入处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
func perPareInsert(name string, age int) {
sqlStr := "insert into user(name, age) values (?, ?)"
stmt, err := DB.Prepare(sqlStr)
if err != nil {
fmt.Printf("Prepare Failed Error %v\n", err)
return
}
defer stmt.Close()
result, err := stmt.Exec(name, age)
if err != nil {
fmt.Printf("Prepare Exec Error %v\n", err)
return
}
InID, err := result.LastInsertId()
if err != nil {
fmt.Printf("Get LastInsertID Error %v \n", err)
return
}
fmt.Printf("Insert ID: %d Success \n", InID)
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
perPareQuery(0)
perPareInsert("大炒肉", 20)
perPareQuery(0)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 输出结果
ID: 1 Name: jicki Age: 11
ID: 2 Name: Jack Age: 30
ID: 4 Name: 小炒肉 Age: 10
ID: 5 Name: 小小肉 Age: 20
Insert ID: 6 Success
ID: 1 Name: jicki Age: 11
ID: 2 Name: Jack Age: 30
ID: 4 Name: 小炒肉 Age: 10
ID: 5 Name: 小小肉 Age: 20
ID: 6 Name: 大炒肉 Age: 20
|
SQL 注入问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func sqlInject(name string) {
sqlStr := fmt.Sprintf("select id,name,age from user where name='%s'", name)
fmt.Println("SQL: ", sqlStr)
var u user
err := DB.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("QueryRow Falied Error %v\n", err)
return
}
fmt.Printf("ID: %d Name: %s Age: %d \n", u.id, u.name, u.age)
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
sqlInject("xxxx' or 1=1#")
}
|
1
2
3
4
5
| # 输出结果
SQL: select id,name,age from user where name='xxxx' or 1=1#'
ID: 1 Name: jicki Age: 11
|
Go 实现 MySQL 事务
- MySQL 只有使用
Innodb
数据引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性, 保证多条联合 SQL
语句要么全部执行, 要么全部不执行(回滚)。
什么是事务
- 事务: 一个最小的不可再分的工作单元, 通常一个事务对应一个完整的业务, 这个完整的业务需要执行多次(增Insert, 删Delete, 改Update)语句共同联合完成。
事务的ACID
通常事务必须满足4个条件既满足(ACID)
Atomictiy
原子性、不可分割性
- 一个事务中的所有操作, 要么全部完成, 要么全部不执行, 不会结束在中间环节。
- 一个事务在执行过程中出现错误, 整个事务都会回滚(Rollback)到事务开始前的状态。
Consistency
一致性
- 事务开始之前和事务结束以后, 数据的完整性没有被破坏。
- 数据写入必须完全符合所规定的规则, 包含数据的精准度、串联性以及后续自发性地完成预定工作。
Isolation
隔离性、独立性
- 数据库允许多个并发事务同时对数据进行读写和修改的能力。
- 隔离性可以防止多个事务并发执行时由于交叉执行导致的数据不一致问题。
- 事务隔离分为不同级别, 包括只读不提交(Read uncommitted)、读提交(Read committed)、可重复读(Repeatable Read)、串行化(Serializable)。
Durability
持久性
- 事务处理结束后, 对数据的修改是永久性的, 故障不丢失。
Go 操作事务的方法
开始事务
1
| func (db *DB) Begin() (*Tx, error)
|
提交事务
1
2
| func (db *DB) Commit() error
|
回滚事务
1
2
| func (tx *Tx) Rollback() error
|
事务操作例子
1
2
3
4
5
6
7
8
| # MySQL 表结果
ID: 1 Name: jicki Age: 11
ID: 2 Name: Jack Age: 18
ID: 4 Name: 小炒肉 Age: 10
ID: 5 Name: 小小肉 Age: 20
ID: 6 Name: 大炒肉 Age: 20
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
func transaction() {
tx, err := DB.Begin()
if err != nil {
if tx != nil {
err = tx.Rollback()
if err != nil {
fmt.Printf("Rollback Failed Error %v\n", err)
return
}
fmt.Printf("Transaction Begin Error %v\n", err)
return
}
}
sqlStr := "update user set age = 18 where id = ?"
rs1, err := tx.Exec(sqlStr, 2)
if err != nil {
err = tx.Rollback()
fmt.Printf("rs1 Exec Failed Error %v\n", err)
if err != nil {
fmt.Printf("Rollback Failed Error %v\n", err)
return
}
}
rowID1, _ := rs1.RowsAffected()
sqlStr = "update user set age = 40 where id = ?"
rs2, err := tx.Exec(sqlStr, 3)
if err != nil {
err = tx.Rollback()
fmt.Printf("rs2 Exec Failed Error %v\n", err)
if err != nil {
fmt.Printf("Rollback Failed Error %v\n", err)
return
}
}
rowID2, _ := rs2.RowsAffected()
if rowID1 == 1 && rowID2 == 1 {
fmt.Println("事务提交....")
err = tx.Commit()
if err != nil {
err = tx.Rollback()
fmt.Printf("Exec Failed Error %v\n", err)
if err != nil {
fmt.Printf("Rollback Failed Error %v\n", err)
return
}
}
} else {
fmt.Println("事务回滚....")
err = tx.Rollback()
if err != nil {
fmt.Printf("Rollback Failed Error %v\n", err)
return
}
}
}
func main() {
err := initDB()
if err != nil {
fmt.Printf("initDB failed, err:%v \n", err)
return
}
defer DB.Close()
transaction()
}
|
sqlx 模块
官方地址 https://github.com/jmoiron/sqlx
sqlx 是 go 标准库 database/sql
的一个扩展库, 在保持 database/sql
标准接口不变的情况下增加了一些扩展。
sqlx 的扩展
可将行记录映射给 struct(内嵌struct也支持)
, map与slices
支持在preprared statement 中使用命名参数
将Get和Select的查询结果保存到struct
和 slice
中
sqlx 模块使用
1
2
| go get -u github.com/jmoiron/sqlx
|
初始化数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| package main
import (
"fmt"
"time"
// 引入 mysql 驱动, 只需要用到 init() 方法
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
// 定义全局的 数据库对象 DB
var DB *sqlx.DB
func initDB() (err error) {
dsn := "root:jicki@tcp(127.0.0.1:13306)/jicki?charset=utf8mb4&parseTime=True"
// 这里 DB 赋值是给 上面定义的全局变量赋值, 不要使用 :=
DB, err = sqlx.Connect("mysql", dsn)
if err != nil {
return
}
// 连接存活时间,超时会被自动关闭
DB.SetConnMaxLifetime(time.Second * 10)
// 最大连接数
DB.SetMaxOpenConns(100)
// 连接池中空闲连接数
DB.SetMaxIdleConns(50)
return
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
}
|
插入数据库
1
2
3
4
5
6
7
8
9
10
11
| /*
数据库 sqlx 表
CREATE TABLE `sqlx` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`age` int(6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
*/
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
// 创建一个结构体,并为 db 打上标签
type user struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
func InsertSqlX(u user) {
// (:后面是数据库字段), 最后传入整个 结构体
sqlStr := "insert into sqlx values (:id, :name, :age)"
result, err := DB.NamedExec(sqlStr, u)
if err != nil {
fmt.Printf("Insert NamedExec Failed Error %v\n", err)
return
}
var TheID int64
TheID, err = result.LastInsertId()
if err != nil {
fmt.Printf("InsertSqlx Get RowsAffected Falied Error %v\n", err)
return
}
fmt.Printf("InsertSqlx Exec Success %d \n", TheID)
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
s1 := user{
Id: 1,
Name: "小炒肉",
Age: 20,
}
s2 := user{
Id: 2,
Name: "大炒肉",
Age: 30,
}
InsertSqlX(s1)
InsertSqlX(s2)
}
|
1
2
3
4
| # 输出结果
InsertSqlx Exec Success 1
InsertSqlx Exec Success 2
|
查询数据
查询单条数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 创建一个结构体,并为 db 打上标签
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
func queryRowToID(id int64) {
sqlStr := "select id, name, age from sqlx where id = ?"
var u User
err := DB.Get(&u, sqlStr, id)
if err != nil {
fmt.Printf("QueryRowToID Get Failed Error %v\n", err)
return
}
fmt.Printf("ID: %d Name: %s Age: %d \n", u.Id, u.Name, u.Age)
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
queryRowToID(1)
}
|
1
2
3
| # 输出结果
ID: 1 Name: 小炒肉 Age: 20
|
查询多条数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // 创建一个结构体,并为 db 打上标签
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
func queryMultiRows(id int64) {
sqlStr := "select id, name, age from sqlx where id > ?"
// 查询的结果存储到 User结构体类型的切片 u 中
var u []User
// Select 方法用于查询多条数据
err := DB.Select(&u, sqlStr, id)
if err != nil {
fmt.Printf("QueryMultiRows Select Failed Error %v\n", err)
return
}
for _, v := range u {
fmt.Printf("ID: %d Name: %s Age: %d \n", v.Id, v.Name, v.Age)
}
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
queryMultiRows(0)
}
|
1
2
3
4
5
| # 输出结果
ID: 1 Name: 小炒肉 Age: 20
ID: 2 Name: 大炒肉 Age: 30
ID: 3 Name: 小小肉 Age: 18
|
查询指定的多条数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // 批量查询
func queryRowByIDs(ids []int) (users []User, err error) {
query, args, err := sqlx.In("select id,name,age FROM sqlx WHERE id IN (?)", ids)
if err != nil {
fmt.Printf("QueryRowByIDs In Failed Error %v\n", err)
return
}
// 使用 Rebind() 重新绑定 组合后的 SQL 语句
query = DB.Rebind(query)
err = DB.Select(&users, query, args...)
for _, user := range users {
fmt.Printf("ID: %d Name: %s Age: %d \n", user.Id, user.Name, user.Age)
}
return
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
// 这里返回一个 []user 的切片 和 一个 error
_, err := queryRowByIDs([]int{1, 3, 5, 6})
if err != nil {
fmt.Printf("QueryRowByIDs Failed Error %v\n", err)
return
}
}
|
1
2
3
4
5
| # 输出结果
ID: 1 Name: 小炒肉 Age: 22
ID: 5 Name: 大肉肉 Age: 48
ID: 6 Name: 中肉肉 Age: 38
|
更新数据
更新数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func updateRow(id, age int64) {
sqlStr := "update sqlx set age = ? where id = ?"
result, err := DB.Exec(sqlStr, age, id)
if err != nil {
fmt.Printf("UpdateRow Exec Failed Error %v\n", err)
return
}
var n int64
n, err = result.RowsAffected()
if err != nil {
fmt.Printf("updateRow Get RowsAffected Failed Error %v\n", err)
return
}
fmt.Printf("update id: %d To age = %d Success Rows: %d \n", id, age, n)
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
queryMultiRows(0)
updateRow(1, 22)
queryMultiRows(0)
}
|
1
2
3
4
5
6
7
8
9
10
| # 输出结果
ID: 1 Name: 小炒肉 Age: 20
ID: 2 Name: 大炒肉 Age: 30
ID: 3 Name: 小小肉 Age: 18
update id: 1 To age = 22 Success Rows: 1
ID: 1 Name: 小炒肉 Age: 22
ID: 2 Name: 大炒肉 Age: 30
ID: 3 Name: 小小肉 Age: 18
|
删除数据
删除数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| func deleteRowToId(id int64) {
sqlStr := "delete from sqlx where id = ?"
result, err := DB.Exec(sqlStr, id)
if err != nil {
fmt.Printf("UpdateRow Exec Failed Error %v\n", err)
return
}
var n int64
n, err = result.RowsAffected()
if err != nil {
fmt.Printf("DeleteRow Get RowsAffected Failed Error %v\n", err)
return
}
fmt.Printf("delete id: %d Success Rows: %d \n", id, n)
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
queryMultiRows(0)
deleteRowToId(3)
queryMultiRows(0)
}
|
1
2
3
4
5
6
7
8
9
| # 输出结果
ID: 1 Name: 小炒肉 Age: 22
ID: 2 Name: 大炒肉 Age: 30
ID: 3 Name: 小小肉 Age: 18
delete id: 3 Success Rows: 1
ID: 1 Name: 小炒肉 Age: 22
ID: 2 Name: 大炒肉 Age: 30
|
事务的操作
事务操作的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
func transaction() (err error) {
tx, err := DB.Begin()
if err != nil {
fmt.Printf("Transaction Begin Failed Error %v \n", err)
return
}
// 这里使用 defer 调用一个 匿名函数, 用于捕获 panic 执行 Rollback 操作
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
fmt.Println("RollBack...")
tx.Rollback()
} else {
err = tx.Commit()
fmt.Println("Commit...")
}
}()
sqlStr1 := "update sqlx set age = 22 where id = ?"
result, err := tx.Exec(sqlStr1, 1)
if err != nil {
return err
}
n, err := result.RowsAffected()
if err != nil {
return err
}
if n != 1 {
return errors.New("exec sqlStr1 Failed")
}
sqlStr2 := "update sqlx set age = 22 where id = ?"
result, err = tx.Exec(sqlStr2, 2)
if err != nil {
return err
}
n, err = result.RowsAffected()
if err != nil {
return err
}
if n != 1 {
return errors.New("exec sqlStr2 Failed")
}
return err
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
if err := transaction(); err != nil {
fmt.Println("Transaction Error ", err)
}
}
|
1
2
3
4
5
| # 输出结果
RollBack...
Transaction Error exec sqlStr1 Failed
|
批量插入数据
sqlx.In 函数
批量插入数据
1
2
3
4
5
6
7
8
9
| # user 表结构如下:
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | | |
| age | int | YES | | 0 | |
+-------+-------------+------+-----+---------+----------------+
|
1
2
3
4
5
6
7
| # User 结构体如下:
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
|
- 使用
sqlx.In
需要对数据映射的结构体实现一个 driver.Valuer
的接口返回值的方法.
1
2
3
| func (u User) Value() (driver.Value, error) {
return []interface{}{u.Name, u.Age}, nil
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| // SqlX.In
func BatchInsertSqlIn(users []interface{}) error {
query, args, _ := sqlx.In("INSERT INTO user (name, age) VALUES (?),(?),(?)",
users...)
fmt.Println(query)
fmt.Println(args)
_, err := DB.Exec(query, args...)
return err
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("InitDB Failed Error %v\n", err)
return
}
u1 := User{
Name: "小肉肉",
Age: 28,
}
u2 := User{
Name: "大肉肉",
Age: 48,
}
u3 := User{
Name: "中肉肉",
Age: 38,
}
// 需要先创建 interface 的切片
users := []interface{}{u1, u2, u3}
if err := BatchInsertSqlIn(users); err != nil {
fmt.Printf("Batch Insert Failed Error %v \n", err)
return
}
queryMultiRows(0)
}
|
1
2
3
4
5
6
7
8
|
INSERT INTO user (name, age) VALUES (?, ?),(?, ?),(?, ?)
[小肉肉 28 大肉肉 48 中肉肉 38]
ID: 1 Name: 小炒肉 Age: 22
ID: 2 Name: 大炒肉 Age: 30
ID: 4 Name: 小肉肉 Age: 28
ID: 5 Name: 大肉肉 Age: 48
ID: 6 Name: 中肉肉 Age: 38
|
NamedExec 函数
批量插入数据
1
2
3
4
5
6
| # User 结构体如下:
type User struct {
Id int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
|
1
2
3
4
5
6
|
// NamedExec
func BatchInsertNamed(users []*User) error {
_, err := DB.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
return err
}
|