Go原生提供了鏈接數據庫操做的支持,在用 Golang進行開發的時候,若是須要在和數據庫交互,則可使用database/sql包。這是一個對關係型數據庫的通用抽象,它提供了標準的、輕量的、面向行的接口。mysql
在Go中訪問數據庫須要用到sql.DB
接口:它能夠建立語句(statement)和事務(transaction),執行查詢,獲取結果。git
使用數據庫時,除了database/sql
包自己,還須要引入想使用的特定數據庫驅動。官方不提供實現,先下載第三方的實現,點擊這裏查看各類各樣的實現版本。github
本文測試數據庫爲mysql,使用的驅動爲:github.com/go-sql-driver/mysql
,須要引入的包爲:golang
"database/sql" _ "github.com/go-sql-driver/mysql"
解釋一下導入包名前面的"_"做用:web
import 下劃線(如:import _ github/demo)的做用:當導入一個包時,該包下的文件裏全部init()函數都會被執行,然而,有些時候咱們並不須要把整個包都導入進來,僅僅是是但願它執行init()函數而已。這個時候就可使用 import _ 引用該包。sql
上面的mysql驅動中引入的就是mysql包中各個init()方法,你沒法經過包名來調用包中的其餘函數。導入時,驅動的初始化函數會調用sql.Register將本身註冊在database/sql包的全局變量sql.drivers中,以便之後經過sql.Open訪問。數據庫
執行數據庫操做以前咱們準備一張表:json
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(45) DEFAULT '', `age` int(11) NOT NULL DEFAULT '0', `sex` tinyint(3) NOT NULL DEFAULT '0', `phone` varchar(45) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
DB, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test") //設置數據庫最大鏈接數 DB.SetConnMaxLifetime(100) //設置上數據庫最大閒置鏈接數 DB.SetMaxIdleConns(10) //驗證鏈接 if err := DB.Ping(); err != nil { fmt.Println("open database fail") return } fmt.Println("connnect success")
sql.Open()中的數據庫鏈接串格式爲:"用戶名:密碼@tcp(IP:端口)/數據庫?charset=utf8"
。tcp
DB的類型爲:*sql.DB
,有了DB以後咱們就能夠執行CRUD操做。Go將數據庫操做分爲兩類:Query
與Exec
。二者的區別在於前者會返回結果,然後者不會。svg
Query
表示查詢,它會從數據庫獲取查詢結果(一系列行,可能爲空)。Exec
表示執行語句,它不會返回行。此外還有兩種常見的數據庫操做模式:
QueryRow
表示只返回一行的查詢,做爲Query
的一個常見特例。Prepare
表示準備一個須要屢次使用的語句,供後續執行用。var user User rows, e := DB.Query("select * from user where id in (1,2,3)") if e == nil { errors.New("query incur error") } for rows.Next(){ e := rows.Scan(user.sex, user.phone, user.name, user.id, user.age) if e != nil{ fmt.Println(json.Marshal(user)) } } rows.Close() //單行查詢操做 DB.QueryRow("select * from user where id=1").Scan(user.age, user.id, user.name, user.phone, user.sex)
總體工做流程以下:
db.Query()
來發送查詢到數據庫,獲取結果集Rows
,並檢查錯誤。rows.Next()
做爲循環條件,迭代讀取結果集。rows.Scan
從結果集中獲取一行結果。rows.Err()
在退出迭代後檢查錯誤。rows.Close()
關閉結果集,釋放鏈接。一般不會約束你查詢必須用Query,只是Query會返回結果集,而Exec不會返回。因此若是你執行的是增刪改操做通常用Exec會好一些。Exec返回的結果是Result
,Result
接口容許獲取執行結果的元數據:
type Result interface { // 用於返回自增ID,並非全部的關係型數據庫都有這個功能。 LastInsertId() (int64, error) // 返回受影響的行數。 RowsAffected() (int64, error) }
若是你如今想使用佔位符的功能,where 的條件想以參數的形式傳入,Go提供了db.Prepare
語句來幫你綁定。準備查詢的結果是一個準備好的語句(prepared statement),語句中能夠包含執行時所需參數的佔位符(即綁定值)。準備查詢比拼字符串的方式好不少,它能夠轉義參數,避免SQL注入。同時,準備查詢對於一些數據庫也省去了解析和生成執行計劃的開銷,有利於性能。
PostgreSQL使用$N
做爲佔位符,N
是一個從1開始遞增的整數,表明參數的位置,方便參數的重複使用。MySQL使用?
做爲佔位符,SQLite兩種佔位符均可以,而Oracle則使用:param1
的形式。
MySQL PostgreSQL Oracle ===== ========== ====== WHERE col = ? WHERE col = $1 WHERE col = :col VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)
stmt, e := DB.Prepare("select * from user where id=?") query, e := stmt.Query(1) query.Scan()
經過db.Begin()
來開啓一個事務,Begin
方法會返回一個事務對象Tx
。在結果變量Tx
上調用Commit()
或者Rollback()
方法會提交或回滾變動,並關閉事務。在底層,Tx
會從鏈接池中得到一個鏈接並在事務過程當中保持對它的獨佔。事務對象Tx
上的方法與數據庫對象sql.DB
的方法一一對應,例如Query,Exec
等。事務對象也能夠準備(prepare)查詢,由事務建立的準備語句會顯式綁定到建立它的事務。
//開啓事務 tx, err := DB.Begin() if err != nil { fmt.Println("tx fail") } //準備sql語句 stmt, err := tx.Prepare("DELETE FROM user WHERE id = ?") if err != nil { fmt.Println("Prepare fail") return false } //設置參數以及執行sql語句 res, err := stmt.Exec(user.id) if err != nil { fmt.Println("Exec fail") return false } //提交事務 tx.Commit()
咱們來一個完整的sql操做:
package main import ( "database/sql" "encoding/json" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/pkg/errors" "strings" ) //數據庫配置 const ( userName = "root" password = "123456" ip = "127.0.0.1" port = "3306" dbName = "test" ) //Db數據庫鏈接池 var DB *sql.DB type User struct { id int64 name string age int8 sex int8 phone string } //注意方法名大寫,就是public func InitDB() { //構建鏈接:"用戶名:密碼@tcp(IP:端口)/數據庫?charset=utf8" path := strings.Join([]string{userName, ":", password, "@tcp(", ip, ":", port, ")/", dbName, "?charset=utf8"}, "") //打開數據庫,前者是驅動名,因此要導入: _ "github.com/go-sql-driver/mysql" DB, _ = sql.Open("mysql", path) //設置數據庫最大鏈接數 DB.SetConnMaxLifetime(100) //設置上數據庫最大閒置鏈接數 DB.SetMaxIdleConns(10) //驗證鏈接 if err := DB.Ping(); err != nil { fmt.Println("open database fail") return } fmt.Println("connnect success") } //查詢操做 func Query() { var user User rows, e := DB.Query("select * from user where id in (1,2,3)") if e == nil { errors.New("query incur error") } for rows.Next() { e := rows.Scan(user.sex, user.phone, user.name, user.id, user.age) if e != nil { fmt.Println(json.Marshal(user)) } } rows.Close() DB.QueryRow("select * from user where id=1").Scan(user.age, user.id, user.name, user.phone, user.sex) stmt, e := DB.Prepare("select * from user where id=?") query, e := stmt.Query(1) query.Scan() } func DeleteUser(user User) bool { //開啓事務 tx, err := DB.Begin() if err != nil { fmt.Println("tx fail") } //準備sql語句 stmt, err := tx.Prepare("DELETE FROM user WHERE id = ?") if err != nil { fmt.Println("Prepare fail") return false } //設置參數以及執行sql語句 res, err := stmt.Exec(user.id) if err != nil { fmt.Println("Exec fail") return false } //提交事務 tx.Commit() //得到上一個insert的id fmt.Println(res.LastInsertId()) return true } func InsertUser(user User) bool { //開啓事務 tx, err := DB.Begin() if err != nil { fmt.Println("tx fail") return false } //準備sql語句 stmt, err := tx.Prepare("INSERT INTO user (`name`, `phone`) VALUES (?, ?)") if err != nil { fmt.Println("Prepare fail") return false } //將參數傳遞到sql語句中而且執行 res, err := stmt.Exec(user.name, user.phone) if err != nil { fmt.Println("Exec fail") return false } //將事務提交 tx.Commit() //得到上一個插入自增的id fmt.Println(res.LastInsertId()) return true } func main() { InitDB() Query() defer DB.Close() }