6.8 KiB
SQLX
Getting Start with SQLite
为了安装sqlx和database driver,可以通过如下命令:
$ go get github.com/jmoiron/sqlx
$ go get github.com/mattn/go-sqlite3
Handle Types
sqlx和database/sql等价,主要有四种类型:
sqlx.DB: 和sql.DB等价,用于表示databasesqlx.Tx: 和sql.Tx等价,用于表示事务sqlx.Stmt: 和sql.Stmt等价,用于表示Prepared Statementsqlx.NamedStmt: 用于表示Prepared Statement with support for named parameters
Handle Types中都嵌入了其database/sql中等价的对象,在调用sqlx.DB.query时,其实际调用的代码和sql.DB.Query。
例如,
sqlx.DB实现中包含了sql.DB的对象引用,在调用sqlx.DB的方法时,实际会调用内部嵌套的sql.DB中的方法。
sqlx中除了上述类型外,还引入了两个cursor类型:
sqlx.Rows: 等价于sql.Rows,为Queryx返回的cursorsqlx.Row: 等价于sql.Row:,由QueryRowx返回的结果
在上述两个cursor类型中,sqlx.Rows嵌入了sql.Rows。但是,由于底层实现不可访问,sqlx.Row对sql.Row的部分内容进行了重新实现,标准接口保持一致。
Connecting to database
一个DB实例代表的只是一个数据库的抽象,其并不代表实际连接。故而,对DB实例进行创建并不会返回error或是抛出panic。
DB实例在内部维护了一个连接池,并且,在初次需要获取连接时去尝试建立连接。
创建sqlx.DB有两种方式,
- 通过
sqlx.DB.Open方法进行创建 - 通过
sqlx.DB.NewDb方法进行创建,该方法接收一个已经存在的sql.DB实例
var db *sqlx.DB
// exactly the same as the built-in
db = sqlx.Open("sqlite3", ":memory:")
// from a pre-existing sql.DB; note the required driverName
db = sqlx.NewDb(sql.Open("sqlite3", ":memory:"), "sqlite3")
// force a connection and test that it worked
err = db.Ping()
Open Db and connect in same time
在某些场景下,可能希望在创建数据库实例时就连接数据库,可以使用如下方法,它们会在创建数据库的同时调用Ping
sqlx.Connect:如果在遇到错误时,会返回errorsqlx.MustConnect:在遇到错误时,会发生panic
var err error
// open and connect at the same time:
db, err = sqlx.Connect("sqlite3", ":memory:")
// open and connect at the same time, panicing on error
db = sqlx.MustConnect("sqlite3", ":memory:")
Quering
handle Types中实现了和database/sql中同样的database方法:
Exec(...) (sql.Result, error): 和database/sql中一致Query(...) (*sql.Rows, error):和database/sql中一致QueryRow(...) *sqlRow:和database/sql中一致
如下是内置方法的拓展:
MustExec() sql.Result: Exec,并且在错误时发生panicQueryx(...) (*sqlx.Rows, error): Query,但是会返回sqlx.RowsQueryRowx(...) *sqlx.Row: QueryRow,但是会返回sqlx.Row
如下是新语意的方法:
Get(dest interface{}, ...) errorSelect(dest interface{}, ...) error
Exec
Exec和MustExec方法会从connection pool中获取连接并且执行提供的sql方法。
并且,在Exec向调用方返回
sql.Result对象之前,连接将会被归还给连接池。
此时,server已经向client发送了query text的执行结果,在根据返回结果构建sql.Result对象之前,会将来凝结返回给连接池。
schema := `CREATE TABLE place (
country text,
city text NULL,
telcode integer);`
// execute a query on the server
result, err := db.Exec(schema)
// or, you can use MustExec, which panics on error
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)
db.Exec返回的结果中包含两部分信息:
LastInsertedId: 在mysql中,该字段在使用auto_increment key时,会返回最后插入的idRowsAffected
bindvars
上述示例中,?占位符在内部调用了bindvars,使用占位符能够避免sql注入。
database/sql并不会对query text做任何验证,其将query text和encoded params原封不动的发送给server。
除非底层driver做了实现,否则query语句会在server端运行sql语句之前执行prepare操作,bindvars每个database可能语法都不相同:
- mysql会使用
?来作为bindvars的占位符 - postgresql会使用
$1, $2来作为bindvars的占位符 - sqlite既接收
?又接收$1 - oracle接收
:name语法
Rebind
可以通过sqlx.DB.Rebind(string) string方法,将使用?的query sql转化为当前数据库类型的query sql。
bindvars is only used for parameterization
bindvars机制只能够被用于参数化,并不允许通过bindvars改变sql语句的结构。例如,如下语句都是不被允许的:
// doesn't work
db.Query("SELECT * FROM ?", "mytable")
// also doesn't work
db.Query("SELECT ?, ? FROM people", "name", "location")
Query
database/sql主要通过Query方法来执行查询语句并获取row results。Query方法会返回一个sql.Rows对象和一个error,
// 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)
}
// check the error from rows
err = rows.Err()
在使用Rows时,应当将其看作是database cursor,而非是结果反序列化之后构成的列表。尽管驱动对结果集的缓存行为各不相同,但是通过Next方法对Rows中的结果进行迭代仍然可以在result set较大的场景下节省内存的使用,因为Next同时只会对一行结果进行扫描。
Scan方法会使用反射将column返回的结果类型映射为go类型(例如string, []byte等)。
在使用
Rows时,如果并不迭代完整个结果集,请确保调用rows.Close()方法将连接返回到连接池中。
Exec和Query在归还连接池上的差异
Exec操作和Query操作在归还连接到连接池的时机有所不同:
Exec:Exec方法在server返回执行结果给client之后,client根据返回结果构建并返回sql.Result之前,将会将连接返回给连接池Query:Query方法和Exec方法不同,其返回信息中包含结果集,必须等待结果集迭代完成或手动调用rows.Close方法之后,才会归还连接给连接池