這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
go提供了一套統一操作database的sql介面,任何第三方都可以通過實現相應的driver來訪問感興趣的資料庫。譬如我們項目中使用的Go-MySQL-Driver。
go提供了一套很好的機制來處理資料庫的查詢操作,譬如官方的例子:
age := 27rows, err := db.Query("SELECT name FROM users WHERE age=?", age)if err != nil { log.Fatal(err)}for rows.Next() { var name string if err := rows.Scan(&name); err != nil { log.Fatal(err) } fmt.Printf("%s is %d\n", name, age)}if err := rows.Err(); err != nil { log.Fatal(err)}
在上面的例子中,Query會返回一個rows,我們通過Next擷取下一行資料,然後使用Scan將每行的資料設定到相應的變數上面去。
雖然這樣操作查詢結果集很方便,但是我們在使用過程中,發現當sql query語句過多,如果每一個查詢都按照這種寫法,代碼量太大了。所以,我們自然就想到了封裝。
db result主要進行的工作就是提供一套統一的介面供外部方便的使用查詢的結果集,它主要提供了如下幾個介面:
func (*Result) GetInt(row, col int) (int64, error)func (*Result) GetIntByName(row int, colName string) (int64, error)func (*Result) GetFloat(row, col int) (float64, error)func (*Result) GetFloatByName(row int, colName string) (float64, error)func (*Result) GetBool(row, col int) (bool, error)func (*Result) GetBoolByName(row int, colName string) (bool, error)func (*Result) GetString(row, col int) (string, error)func (*Result) GetStringByName(row int, colName string) (string, error)func (*Result) GetBuffer(row, col int) ([]byte, error)func (*Result) GetBufferByName(row int, colName string) ([]byte, error)
可以看到,result的“Get*”介面通過結果集的row和col的索引來訪問資料,而"Get*ByName"介面則是通過row以及col的名字來訪問資料。
因為go支援很多的資料類型,為了簡單起見,所有int類型的我們統一使用int64代替,外部在進行相應的資料轉換。同理float類型也是用float64代替。如果查詢的結果某個欄位為空白,result只是返回該欄位預設的數值。通常情況下,我都會要求資料庫中的欄位都為not null,所以查詢欄位為null的情況這裡沒有過多考慮。
一個很簡單的例子:
//msg_id bigint//msg varchar(256)res, err := Query("is", "select msg_id, msg from msg_table where msg_id in (?, ?)", 1, 2)var id1 int64id1, err = res.GetInt(0, 0)var msg2 stringmsg2, err = res.GetStringByName(1, "msg")
在上面的例子中,我們在查詢的時候返回了一個result,然後通過相關的函數擷取到了相應的資訊。這裡我們需要特別注意的就是Query後面的第一個參數“is”。
在Go-MySQL-Driver中,會兩種結果集模式,一個是text rows,另一個則是binary rows(使用stmt query的結果集),在text rows中,所有的資料都是[]byte格式,而在binary rows中,則會根據stmt對應的結果類型轉換成相應的資料。為了統一,我們通過在query的時候手動指定column types,在row scan的時候直接建立對應的類型資料,供scan設定。如下:
func (res *Result) newValue(column int) (interface{}, error) { t := res.ColumnTypes[column] switch t { case Column_String: return new(string), nil case Column_Int: return new(int64), nil case Column_Bool: return new(bool), nil case Column_Float: return new(float64), nil case Column_Buffer: return new([]byte), nil default: return nil, ErrColumnTypes }}//遍曆結果集的時候,我們根據column types產生指定的value,並通過Scan設定for rows.Next() { dest := make([]interface{}, len(res.ColumnNames)) for i, _ := range dest { dest[i], err = res.newValue(i) if err != nil { return err } } err = rows.Scan(dest...) if err != nil { return err }
如果大家使用過mysql_stmt_bind_result,就可以發現,column types的概念其實就跟設定MYSQL_BIND差不多,只是result為了簡單,只支援int64,float64,bool,string,以及[]byte這幾種類型。
具體的代碼在這裡https://github.com/siddontang/qpush/blob/master/go/src/lib/db/result.go。