关于在Go中如何使用SQL:

  • [go-database-sql教程]

如果你要从了解Go语言开始的话,可以参考:

因为sqlx会包含所有database/sql已有的接口,所以本文中所有关于database/sql的使用建议也同样适用于sqlx。

安装

开始之前,你需要安装sqlx和Go数据库驱动。建议从sqlite3开始入门:

$ go get github.com/jmoiron/sqlx
$ go get github.com/mattn/go-sqlite3

handle类型

sqlx最大可能去实现database/sql一样的功能。有4中主要handle类型:

  • sqlx.DB - 相当于database/sql中的sql.DB,代表一个数据库。
  • sqlx.Tx - 相当于database/sql中的sql.Tx,代表一个事务。
  • sqlx.Stmt = 相当于database/sql中的sql.Stmt,代表一条要执行的语句。
  • sqlx.NamedStmt - 代表一条有参数的执行语句。

所有handle类型内嵌实现了对应database/sql中handle。也就是说,当你在代码中调用sqlx.DB.Query的时候,同时也会调用执行到sql.DB.Query对应的代码。

除此之外,还有两种指针类型:

  • sqlx.Rows - 相当于sql.Rows, 从Queryx返回的指针;
  • sqlx.Row - 相当于sql.Row, 从QueryRowx返回的结果;

跟handle类型一样,sqlx.Rows内嵌了sql.Rows。犹豫sql.Row的底层实现未公开,sqlx.Row只是实现了sql.Row的部分标准接口。

连接数据库

DB本身并不是一个数据库连接,而是数据库的一个抽象表示。这也是为什么单独创建DB的时候不会报错。DB内部维护了一个连接池,会根据需要自动尝试去建立连接

两种创建sqlx.DB的方式:

  • 1)通过Open创建;
  • 2)通过NewDB从现有的sql.DB创建。
var db *sqlx.DB
// 方式1:使用Open函数,跟使用内置的sql.Open一样
db = sqlx.Open("sqlite3", ":memory:")
// 方式2:使用NewDB从sql.DB创建,driverName必须指定
db = sqlx.NewDb(sql.Open("sqlite3", ":memory:"), "sqlite3")
// 建立并测试连接
err = db.Ping()
ConnectMustConnect
var err error
// 同时打开并连接数据库,返回错误信息
db, err = sqlx.Connect("sqlite3", ":memory:")
// 同时打开并连接数据库,遇到错误终止程序
db = sqlx.MustConnect("sqlite3", ":memory:")
查询 Query
sqlxhandledatabase/sql
database/sqldatabase/sqldatabase/sql
sqlxdatabase/sql
panicsqlx.Rowssqlx.Row

sqlx还引入了两个新的操作:

  • Get(dest interface{}, …) error
  • Select(dest interface{}, …) error

我们逐个来讲解。

Exec

ExecMustExec(ad-hoc query)(prepared statement)
schema := `CREATE TABLE place (
    country text,
    city text NULL,
    telcode integer);`
// 直接执行sql操作
result, err := db.Exec(schema)
// 或者,通过MustExec执行,错误时抛出panic异常
cityState := `INSERT INTO place (country, telcode) VALUES (?, ?)`
countryCity := `INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)`

db.MustExec(cityState, "Hong Kong", 852)
db.MustExec(cityState, "Singapore", 65)
db.MustExec(countryCity, "South Africa", "Johannesburg", 27)

LastInsertedId()RowsAffected()LastInsertedId()RETURNING

bindvars

“?”“bindvars”database/sql
?1,2?$1:name
sqlx.DB.Rebind(string) string?

一个常见的误区是把binvars用作数值插入。上面讨论的占位符只用来参数化,并不允许修改sql语句的语法结构。比如,用bindvars来尝试参数化列名和表名都是不工作的:

// doesn't work
db.Query("SELECT * FROM ?", "mytable")
// also doesn't work
db.Query("SELECT ?, ? FROM people", "name", "location")

Query

sql.Rowserror
// fetch all places from the db
rows, err := db.Query("SELECT country, city, telcode FROM place")
// iterate over each row
for rows.Next() {
    var country string
    // note that city can be NULL, so we use the NullString type
    var city    sql.NullString
    var telcode int
    err = rows.Scan(&country, &city, &telcode)
}
RowsNext()Sacn()string []byte
rowsrows.Close()
Queryerror
row.Scanrow.Scansql.RawBytesNext()sql.RawBytes
QueryNextrows.Close()

sqlx中的扩展Queryx跟sql中Query的行为是一样的,但是返回的结果是sqlx.Rows,同样对scan做了增强:

type Place struct {
    Country       string
    City          sql.NullString
    TelephoneCode int `db:"telcode"`
}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    var p Place
    err = rows.StructScan(&p)
}

sqlx.Rows的主要扩展是StructScan(),它会自动扫码返回结果然后存到对应的结构体字段中。你也可以只用db结果标签来表明哪个列名对应哪个字段,或者使用db.MapperFunc()设置一个默认映射函数。默认的映射规则是直接将结构体中的字段名使用strings.Lower转变为小写后直接用作数据表的列名。更多信息可以查看高级扫描部分。

QueryRow

QueryRow用来从服务器获取一行数据。它首先从连接池获取一个数据库连接,然后使用Query执行sql查询,并返回一个Row对象;其中Row对象内部又有自己的Rows对象。

row := db.QueryRow("SELECT * FROM place WHERE telcode=?", 852)
var telcode int
err = row.Scan(&telcode)

跟Query不同的是,QueryRow成功的时候只返回一个Row对象,这样可以很安全地使用链式扫码返回结果。如果查询出错,会返回一个错误error。如果没有数据,返回sql.ErrNoRows。如果扫码本身出错,同样会返回错误error。

返回结果Row里的Rows结构会随着扫描而释放,也就是说,数据库连接在结果被扫描后会马上释放。也意味着sql.RawBytes在这里是不能用的,因为指向的内存可能已经释放了。

同样的,sqlx中的扩展QueryRowx会返回一个sqlx.Row,而不是sql.Row;并且实现了和Rows一样的扫描扩展。

var p Place
err := db.QueryRowx("SELECT city, telcode FROM place LIMIT 1").StructScan(&p)

Get和Select

GetSelect 是 handel类型扩展, 将查询执行与扫描动作合并,为了解释清除他们,我们讲解下什么是可以扫描的类型。

  • 非结构体(struct)类型的 ,比如 string, int 基础类型等,可扫描
  • 实现了 sql.Scanner 接口的,
  • 没有导出字段的结构,例如 time.Time ,可扫描

Get and Select use rows.Scan on scannable types and rows.StructScan on non-scannable types. They are roughly analagous to QueryRow and Query, where Get is useful for fetching a single result and scanning it, and Select is useful for fetching a slice of results:

p := Place{}
pp := []Place{}
 
// this will pull the first place directly into p
err = db.Get(&p, "SELECT * FROM place LIMIT 1")
 
// this will pull places with telcode > 50 into the slice pp
err = db.Select(&pp, "SELECT * FROM place WHERE telcode > ?", 50)
 
// they work with regular types as well
var id int
err = db.Get(&id, "SELECT count(*) FROM place")
 
// fetch at most 10 place names
var names []string
err = db.Select(&names, "SELECT name FROM place LIMIT 10")

Get and Select both will close the Rows they create during query execution, and will return any error encountered at any step of the process. Since they use StructScan internally, the details in the advanced scanning section also apply to Get and Select.

Select can save you a lot of typing, but beware! It's semantically different from Queryx, since it will load the entire result set into memory at once. If that set is not bounded by your query to some reasonable size, it might be best to use the classic Queryx/StructScan iteration instead.

事务操作Transaction

参考