golang 數據庫開發神器 sqlx使用指南

sqlx使用指南

這邊文章主要基於Illustrated guide to SQLX翻譯而成。
sqlx是一個go語言包,在內置database/sql包之上增長了不少擴展,簡化數據庫操做代碼的書寫。html

資源

若是對於go語言的sql用法不熟悉,能夠到下面網站學習:
database/sql documentation
go-database-sql tutorialgit

若是對於golang語言不熟悉,能夠到下面網站學習:
The Go tour
How to write Go code
Effective Go
CSDNgithub

因爲database/sql接口是sqlx的子集,當前文檔中全部關於database/sql的用法一樣用於sqlxgolang

開始

安裝sqlx 驅動sql

$ go get github.com/jmoiron/sqlx
  •  

本文訪問sqlite數據庫數據庫

$ go get github.com/mattn/go-sqlite3
  •  

Handle Types

sqlx設計和database/sql使用方法是同樣的。包含有4中主要的handle types:
- sqlx.DB - 和sql.DB類似,表示數據庫。
- sqlx.Tx - 和sql.Tx類似,表示transacion。
- sqlx.Stmt - 和sql.Stmt類似,表示prepared statement。
- sqlx.NamedStmt - 表示prepared statement(支持named parameters)緩存

全部的handler types都提供了對database/sql的兼容,意味着當你調用sqlx.DB.Query時,能夠直接替換爲sql.DB.Query.這就使得sqlx能夠很容易的加入到已有的數據庫項目中。併發

此外,sqlx還有兩個cursor類型:
- sqlx.Rows - 和sql.Rows相似,Queryx返回。
- sqlx.Row - 和sql.Row相似,QueryRowx返回。app

連級到數據庫

一個DB實例並非一個連接,可是抽象表示了一個數據庫。這就是爲何建立一個DB時並不會返回錯誤和panic。它內部維護了一個鏈接池,當須要進行鏈接的時候嘗試鏈接。你能夠經過Open建立一個sqlx.DB或經過NewDb從已存在的sql.DB中建立一個新的sqlx.DBide

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()
  •  

在一些環境下,你可能須要同時打開一個DB並連接。能夠調用connect,這個函數打開一個新的DB並嘗試PingMustConnect函數在連接出錯時會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:")
  •  

Querying 101

sqlx中的handle types實現了數據庫查詢相同的基本的操做語法。
- Exec(…) (sql.Result, error) - 和 database/sql相比沒有改變
- Query(…) (*sql.Rows, error) - 和 database/sql相比沒有改變
- QueryRow(…) *sql.Row - 和 database/sql相比沒有改變

對內置語法的擴展
- MustExec() sql.Result – Exec, but panic on error
- Queryx(…) (*sqlx.Rows, error) - Query, but return an sqlx.Rows
- QueryRowx(…) *sqlx.Row – QueryRow, but return an sqlx.Row

還有下面新的語法

  • Get(dest interface{}, …) error
  • Select(dest interface{}, …) error
    下面會詳細介紹這些方法的使用

Exec

Exec和MustExec從鏈接池中獲取一個鏈接而後只想對應的query操做。對於不支持ad-hoc query execution的驅動,在操做執行的背後會建立一個prepared statement。在結果返回前這個connection會返回到鏈接池中。

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)
  •  

上面代碼中 result有兩個可能的數據LastInsertId() or RowsAffected(),依賴不一樣的驅動。
Mysql中,在含有auto-increment key的表中執行插入操做會獲得LastInsertId(),在PostgreSQL中這個信息只有在使用RETURNING語句的row cursor中才會返回。

bindvars

代碼中佔位符,稱爲bindvars,很是重要,你能夠老是使用它們來向數據庫發送數據,能夠用來組織SQL Injection攻擊。
database/sql並不會對查詢語句進行任何的校驗,傳入什麼就發送到server是什麼。
除非driver實現特定的接口,query在數據庫執行以前會準備好。不一樣的數據庫的bindvars不同。
- MySQL 使用
- PostgreSQL 使用1,1,2等等
- SQLite 使用$1
- Oracle 使用:name

其餘數據庫可能還不同。你可使用sqlx.DB.Rebind(string) string函數利用?語法來獲得一個適合在當前數據庫上執行的query語句。

關於bindvars常見的誤解是他們用於插值。他們只用於參數化,不容許改變sql語句的合法接口。例如,下面的用法是會報錯的

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

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

Query

Query是database/sql中執行查詢主要使用的方法,該方法返回row結果。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)
}
  •  

在使用的時候應該吧Rows當成一個遊標而不是一系列的結果。儘管數據庫驅動緩存的方法不同,經過Next()迭代每次獲取一列結果,對於查詢結果很是巨大的狀況下,能夠有效的限制內存的使用,Scan()利用reflect把sql每一列結果映射到go語言的數據類型如string,[]byte等。若是你沒有遍歷徹底部的rows結果,必定要記得在把connection返回到鏈接池以前調用rows.Close()

Query返回的error有多是在server準備查詢的時候發生的,也有多是在執行查詢語句的時候發生的。例如可能從鏈接池中獲取一個壞的連級(儘管數據庫會嘗試10次去發現或建立一個工做鏈接)。通常來講,錯誤主要由錯誤的sql語句,錯誤的相似匹配,錯誤的域名或表名等。

在大部分狀況下,Rows.Scan()會把從驅動獲取的數據進行拷貝,不管驅動如何使用緩存。特殊類型sql.RawBytes能夠用來從驅動返回的數據總獲取一個zero-copy的slice byte。當下一次調用Next的時候,這個值就不在有效了,由於它指向的內存已經被驅動重寫了別的數據。

Query使用的connection在全部的rows經過Next()遍歷完後或者調用rows.Close()後釋放。
QueryxQuery行爲很類似,不過返回一個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.Rowx的主要擴展就是StructScan,能夠自動把查下結果掃描到對應結構體中的域(fileld)中。注意結構體中域(field)必須是可導出(exported)的,這樣sqlx纔可以寫入值到結構體中。
正如在上面代碼中所示,能夠利用db結構體標籤來指定結構體field映射到數據庫中特定的列名,或者用db.MapperFunc()來指定默認的映射。db默認對結構體的filed名執行strings.Lower後,和數據庫的列名進行匹配。關於StructScan,SliceScan,MapScan更詳細的內容請參見後面章節advanced scanning

QueryRow

QueryRow從數據庫server中獲取一列數據。它從鏈接池中獲取一個連級,而後執行Query,返回一個Row對象,這個對象有一個本身的內部的Rows對象。

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

不像QueryQueryRow只返回一個Row類型,並不返回error,若是在執行查詢過程當中出錯,則錯誤經過Scan返回,若是查詢結果爲空,則返回sql.ErrNoRows。若是Scan自己出錯,error一樣由scan返回。

QueryRow使用的connection當result返回的時候就關閉了,也就意味着使用QueryRow的時候不可以使用sql.RawByes,由於driver使用sql.RawBytes引用內存,在connection回收後可能也會無效。

QueryRowx返回一個sqlx.Row而不是sql.Row,它實現了跟Rows相同的scan方法如上,同時還有高級的scan方法以下:(更高級的scan方法advanced scanning section

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

Get and Select

GetSelect是一個很是省時的擴展。它們把query和很是靈活的scan語法結合起來。爲了更加清晰的介紹它們,咱們先討論下什麼是scannalbe

  • a value is scannable if it is not a struct, eg string, int
  • a value is scannable if it implements sql.Scanner
  • a value is scannable if it is a struct with no exported fields (eg. time.Time)

GetSelect對scannable的類型使用rows.scan,對non-scannable的類型使用rows.StructScanGet用來獲取單個結果真後Scan,Select用來獲取結果切片。

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")
  •  

GetSelect在執行完查詢後就會關閉Rows,而且在執行階段遇到任何問題都會返回錯誤。因爲它們內部使用的StructScan,因此 下文中advanced scanning section講的特徵也適用與Get和Select。

Select能夠提升編碼小路,可是要注意SelectQueryx是有很大不一樣的,由於Select會把整個結果一次放入內存。若是查詢結果沒有限制特定的大小,那麼最好使用Query/StructScan迭代方法。

Transactions

爲了使用transactions,必須使用DB.Begin()來建立,下面的代碼是錯誤的:

db.MustExec("BEGIN;")
db.MustExec(...)
db.MustExec("COMMIT;")

Exec和其餘查詢語句會向DB請求一個connection,執行完後就返回到鏈接池中,並不能保證每次獲取的connection就是BEGIN執行時使用的那個,因此正確的作法要使用DB.Begin:

tx, err := db.Begin()
err = tx.Exec(...)
err = tx.Commit()

DB除了Begin以外,還可使用擴展Beginx()MustBegin(),返回sqlx.Tx

tx := db.MustBegin()
tx.MustExec(...)
err = tx.Commit()

sqlx.Tx擁有sqlx.DB擁有的全部的handle extensions.
因爲transaction是一個connection狀態,因此Tx對象必須綁定和控制單個connection。一個Tx會在整個生命週期中保存一個connection,而後在調用commitRollback()的時候釋放掉。你在調用這幾個函數的時候必須十分當心,不然connection會一直被佔用直到被垃圾回收。
因爲在一個transaction中只能有一個connection,因此每次只能執行一條語句。在執行另外的query操做以前,cursor對象Row*和Rows必須被ScannedClosed。若是在數據庫給你返回數據的時候你嘗試向數據庫發送數據,這個操做可能會中斷connection。

最後,Tx對象僅僅執行了一個BEGIN語句和綁定了一個connection,它其實並無在server上執行任何操做。而transaction真實的行爲包含locking和isolation,在不一樣數據庫上實現是不一樣的。

Prepared Statements

對於大部分的數據庫來講,當一個query執行的時候,在數據庫內部statements其實已經準備好了。而後你能夠經過sqlx.DB.Prepare()準備statements,便於後面在別的地方使用。

stmt, err := db.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = stmt.QueryRow(65)

tx, err := db.Begin()
txStmt, err := tx.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = txStmt.QueryRow(852)

Prepare實際上在數據庫上執行preparation操做,因此它須要一個connection和它的connection state。
database/sql把這部分進行了抽象,自動在新的connection上建立statement,這樣開發者就能經過stmt對象在多個connection上併發執行操做。
Preparex()返回一個sqlx.Stmt對象,包含sqlx.DB和sqlx.Tx全部的handle 擴展(方法)。

sql.Tx對象含有一個Stmt()方法,從已存在的statement中返回一個特定於改transaction的statement。
sqlx.Tx一樣含有一個Stmtx()方法,從已有的sql.Stmt或sqlx.Stmt中建立一個特定於transaction的sqlx.Stmt。

Query Helpers

「In」 Queries

因爲database/sql並不會分析你的查詢語句而後直接把參數傳遞給driver,這樣對於IN

相關文章
相關標籤/搜索