目录

关于Go-Sqlx模块的使用和注意事项

sqlx可以认为是Go语言内置database/sql的超集,它在优秀的内置database/sql基础上提供了一组扩展。 示例源码主要来源于组件作者GitHub。

引入扩展内容

1
2
3
4
5
6
7
import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

连接数据库

1
2
3
4
5
6
7
8
// 相关参数自行写死或者读取自己写的配置信息
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true", define.MysqlUser, define.MysqlPasswd, define.MysqlAddr, define.MysqlPort, define.MysqlDB, define.MysqlCharSet)
db, err := sqlx.Connect("mysql", dsn)
if err != nil {
    fmt.Println(err)
}
// 正常使用,用完记得断开链接
defer db.Close()

数据表创建

注意,Must相关方法是为了简化错误处理而出现的,当开发者确定SQL操作不会返回错误的时候就可以使用Must方法,但是如果真的出现了未知错误的时候,这个方法内部会触发panic,开发者需要有一个兜底的方案来处理这个panic,比如使用recover。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建数据表内容
var schema = `
CREATE TABLE person (
    first_name text,
    last_name text,
    email text
);

CREATE TABLE place (
    country text,
    city text NULL,
    telcode integer
)`
db.MustExec(schema)
tx := db.MustBegin()
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)", "Jason", "Moiron", "jmoiron@jmoiron.net")
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)", "John", "Doe", "johndoeDNE@gmail.net")
tx.MustExec("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)", "United States", "New York", "1")
tx.MustExec("INSERT INTO place (country, telcode) VALUES (?, ?)", "Hong Kong", "852")
tx.MustExec("INSERT INTO place (country, telcode) VALUES (?, ?)", "Singapore", "65")
tx.Commit()

事务的使用

注意,扩展Beginx()和MustBegin(),返回sqlx.Tx。

由于transaction是一个connection状态,所以Tx对象必须绑定和控制单个connection。一个Tx会在整个生命周期中保存一个connection,然后在调用commit或Rollback()的时候释放掉。你在调用这几个函数的时候必须十分小心,否则connection会一直被占用直到被垃圾回收。

由于在一个transaction中只能有一个connection,所以每次只能执行一条语句。在执行另外的query操作之前,cursor对象Row*和Rows必须被Scanned或Closed。如果在数据库给你返回数据的时候你尝试向数据库发送数据,这个操作可能会中断connection。

最后,Tx对象仅仅执行了一个BEGIN语句和绑定了一个connection,它其实并没有在server上执行任何操作。而transaction真实的行为包含locking和isolation,在不同数据库上实现是不同的。

1
2
3
4
5
6
tx, err := db.Begin()
err = tx.Exec(...)
err = tx.Commit()
if err != nil{
	tx.Rollback()
}

查询数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 创建结构体,记得元素首字母大写
type Person struct {
FirstName string `db:"first_name"`
LastName  string `db:"last_name"`
Email     string
}

type Place struct {
Country string
City    sql.NullString
TelCode int
}

单条数据查询(Get方法)

1
2
3
4
// 注意参数类型
jason = Person{}
err = db.Get(&jason, "SELECT * FROM person WHERE first_name=?", "Jason")
fmt.Printf("%#v\n", jason)

多条数据查询(Select方法)

1
2
3
4
5
6
7
8
// 注意参数类型
places := []Place{}
err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
if err != nil {
    fmt.Println(err)
    return
}
usa, singsing, honkers := places[0], places[1], places[2]

插入数据、更新数据、删除数据

可以直接通过NamedExec()或者Exec()方法来执行语句

1
2
3
result,err := tx.Exec("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)", "Jason", "Moiron", "jmoiron@jmoiron.net")
result.LastInsertId() //返回插入数据后的主键Id值
result.RowsAffected() //返回执行SQL后,影响的数据行数

绑定结构体操作(NamedXXX方法)

有时候如果sql语句中的占位符过多,后面我们传参容易传错。我们可以用该方法用来绑定SQL语句与结构体或map中的同名字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 单条数据插入示例
_, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`, 
    map[string]interface{}{
        "first": "Bin",
        "last": "Smuth",
        "email": "bensmith@allblacks.nz",
})

// 批量插入示例
personMaps := []map[string]interface{}{
{"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"},
{"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"},
{"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"},
}
_, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
        VALUES (:first_name, :last_name, :email)`, personMaps)

// 查询示例
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})

rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)

注意事项

结构体元素首字母没有大写

这个会导致get failed, err:scannable dest type struct with >1 columns (3) in result。这类型错误,可以从代码中发现, 当db.Get方法执行时,sqlx包中会访问对应结构体中的各字段,这时发现字段全部为小写,不可访问,即报错了。我们修改为大写即解决了问题。

查询出来的字段包含Null

这个会导致converting NULL to string is unsupported这类型错误,可以通过在设置结构体属性类型时,改为对应的sql.Null*类型,例如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 修改前
type Person struct {
FirstName string `db:"first_name"`
LastName  string `db:"last_name"`
Email     string
}

// 修改后
type Person struct {
FirstName sql.NullString `db:"first_name"`
LastName  sql.NullString `db:"last_name"`
Email     sql.NullString
}

但是不建议这么做,会导致读出来的Null数据为false,网上也有提到通过Sql中IFNULL() 方法来给默认值的,除非情况特殊,要不然还是一开始设计数据表的时候,设置为NOT NULL,给个初始默认值,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。

组件GitHub地址

地址:https://github.com/jmoiron/sqlx

参考文章一:阿里云搭建Let’s encrypt通配符证书