原創做者,公衆號【程序員讀書】,歡迎關注公衆號,轉載文章請註明出處哦。mysql
應該說,數據庫編程是任何編程語言都有提供的基礎功能模塊,不管是編程語言內置的支持,仍是經過外部庫來實現;固然啦,不一樣編程語言提供的數據庫編程API是不盡相同的,並且須要支持的數據庫也是多種多樣,如經常使用的MySQL
,SQLServer
,Postgres
等數據庫。git
拋開其餘編程語言不談,在這篇文章中,咱們就來聊一聊Go語言數據庫編程的那些事,瞭解如何使用Go語言提供的標準庫,編寫通用的操做數據庫的代碼。程序員
標準庫database/sql
是Go語言的數據庫操做抽象層,具體的數據庫操做邏輯實現則由不一樣的第三方包來作,而標準庫database/sql/driver
提供了這些第三方包實現的標準規範。github
因此,Go語言的數據庫編程,通常只須要導入標準庫database/sql
包,這個包提供了操做數據庫全部必要的結構體、函數與方法,下面是導入這個包的語句:golang
import database/sql
複製代碼
Go語言這樣作的好處就是,當從一個數據庫遷移到另外一個數據庫時(如SQL Server
遷移到MySQL
),則只須要換一個驅動包即可以了。sql
前面咱們說Go語言數據操做的由不一樣第三方包來實現,那麼若是咱們想要鏈接MySQL數據庫的話,要怎麼實現一個這樣的包呢?實際上,Go語言標準庫database/sql/driver
定義了實現第三方驅動包的全部接口,咱們只導入實現了database/sql/driver
相關接口驅動包就能夠了。數據庫
下面是支持Golang的數據庫驅動列表:編程
以MySQL數據庫驅動包爲例:服務器
$ go get -u github.com/go-sql-driver/mysql
複製代碼
import database/sql
import _ "github.com/go-sql-driver/mysql"
複製代碼
sql.DB
結構是sql/database
包封裝的一個數據庫操做對象,包含了操做數據庫的基本方法。
DSN
全稱爲Data Source Name
,表示數據庫連來源,用於定義如何鏈接數據庫,不一樣數據庫的DSN格式是不一樣的,這取決於數據庫驅動的實現,下面是go-sql-driver/sql
的DSN格式,以下所示:
//[用戶名[:密碼]@][協議(數據庫服務器地址)]]/數據庫名稱?參數列表
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
複製代碼
database/sql
包中有兩個函數用於初始化並返回一個*sql.DB
結構體對象:
//driverName表示驅動名,如mysql,dataSourceName爲上文介紹的DSN
func Open(driverName, dataSourceName string) (*DB, error)
func OpenDB(c driver.Connector) *DB
複製代碼
下面演示如何使用這兩個函數:
通常而言,咱們使用Open()
函數即可初始化並返回一個*sql.DB
結構體實例,使用Open()
函數只要傳入驅動名稱及對應的DSN即可,使用很簡單,也很通用,當須要鏈接不一樣數據庫時,只須要修改驅動名與DSN就能夠了。
import "database/sql"
import _ "github.com/go-sql-driver/mysql" //注意前面有_
func open(){
const DRIVER = "mysql"
var DSN = "root:123456@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local"
var err error
db, err = sql.Open(DRIVER, DSN)
if err != nil {
panic(err)
}
if err = db.Ping();err != nil{
panic(err)
}
}
複製代碼
OpenDB()
函數則依賴驅動包實現年sql/database/driver
包中的Connector
接口,這種方法並不通用化,不推薦 使用,下面演示使用mysql
驅動包的driver.Connector
初始化並返回*sql.DB
結構體實例:
import "database/sql"
import "github.com/go-sql-driver/mysql"//注意前面沒有_
func openConnector() {
Connector, err := mysql.NewConnector(&mysql.Config{
User: "root",
Passwd: "123456",
Net: "tcp",
Addr: "localhost:3306",
DBName: "test",
AllowNativePasswords:true,
Collation:"utf8_general_ci",
ParseTime:true,
Loc:time.Local,
})
if err != nil {
panic(err)
}
db = sql.OpenDB(Connector)
if err = db.Ping();err != nil{
panic(err)
}
}
複製代碼
使用前面定義的方法,初始化一個*sql.DB
指針結構體:
var db *sql.DB
//在init方法初始化`*sql.DB`
func init(){
open()
//或者
openConnector()
}
複製代碼
這裏要說一下的是,sql.DB
是sql/database
包封裝的一個結構體,但不表示一個數據庫鏈接對象,實際上,咱們能夠把sql.DB
看做一個簡單的數據庫鏈接池,咱們下面的幾個方法設置數據庫鏈接池的相關參數:
func (db *DB) SetMaxIdleConns(n int)//設置鏈接池中最大空閒數據庫鏈接數,<=0表示不保留空閒鏈接,默認值2
func (db *DB) SetMaxOpenConns(n int)//設置鏈接池最大打開數據庫鏈接數,<=表示不限制打開鏈接數,默認爲0
func (db *DB) SetConnMaxLifetime(d time.Duration)//設置鏈接超時時間
複製代碼
代碼演示
db.SetMaxOpenConns(100)//設置最多打開100個數據連鏈接
db.SetMaxIdleConns(0)//設置爲0表示
db.SetConnMaxLifetime(time.Second * 5)//5秒超時
複製代碼
下面咱們演示在Go語言中有關數據庫的增刪改查(CURD)等基本的操做,爲此咱們建立了一個名爲users
的數據表,其建立的SQL
語句以下:
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
username VARCHAR(32) NOT NULL COMMENT '用戶名',
moeny INT DEFAULT 0 COMMENT '帳戶餘額',
PRIMARY KEY(id)
);
INSERT INTO users VALUES(1,'小明',1000);
INSERT INTO users VALUES(2,'小紅',2000);
INSERT INTO users VALUES(3,'小剛',1400);
複製代碼
查詢是數據庫操做最基本的功能,在Go語言中,可使用sql.DB
中的Query()
或QueryContext()
方法,這兩個方法的定義以下:
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
複製代碼
Query()
和QueryContext()
方法返回一個sql.Rows
結構體,表明一個查詢結果集,sql.Rows
的定義及其所包含的方法以下:
type Rows struct {
//contains filtered or unexported fields
}
func (rs *Rows) Close() error //關閉結果集
func (rs *Rows) ColumnTypes() ([]*ColumnType, error)//返回數據表的列類型
func (rs *Rows) Columns() ([]string, error)//返回數據表列的名稱
func (rs *Rows) Err() error//錯誤集
func (rs *Rows) Next() bool//遊標,下一行
func (rs *Rows) NextResultSet() bool
func (rs *Rows) Scan(dest ...interface{}) error //掃描結構體
複製代碼
使用sql.Rows
的Next()
和Scan
方法,但能夠遍歷返回的結果集,下面是示例代碼:
func query() {
selectText := "SELECT * FROM users WHERE id = ?"
rows, _ := db.Query(selectText, 2)
defer rows.Close()
for rows.Next() {
var (
id int
username string
money int
)
_ = rows.Scan(&id, &username,&money)
fmt.Println(id, username,money)
}
}
複製代碼
還能夠用sql.DB
中的QueryRow()
或QueryRowContext()
方法,這兩個方法的定義以下所示:
func (db *DB) QueryRow(query string, args ...interface{}) *Row
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
複製代碼
QueryRow
和QueryRowContext
返回一個sql.Row
結構體,表明數據表的一行,sql.Row
的定義以下,能夠看到sql.Row
結構體只有一個Scan()
方法,用於掃描sql.Row
結構體中的數據。
type Row struct{
}
func (r *Row) Scan(dest ...interface{}) error
複製代碼
代碼演示
func queryRow(){
selectText := "SELECT * FROM users WHERE id = ?"
row := db.QueryRow(selectText, 2)
var (
id int
username string
money int
)
_ = row.Scan(&id, &username,&money)
fmt.Println(id, username,money)
}
複製代碼
另外,使用sql.DB
中的Prepare()
或PrepareContext()
方法,能夠返回一個sql.Stmt
結構體`。
注意:sql.Stmt結構體會先把在
Prepare()
或PrepareContext()
定義的SQL語句發給數據庫執行,再將SQL語句中須要的參數發給數據庫,再返回處理結果。
func (db *DB) Prepare(query string) (*Stmt, error)
func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)
複製代碼
sql.Stmt
提交了與sql.DB
用於查詢並返回結果集的方法,下面請看示例:
func queryStmt(){
stmt,err := db.Prepare("SELECT * FROM users WHERE id = ?")
if err != nil{
return
}
defer stmt.Close()
rows,err := stmt.Query(2)
defer rows.Close()
for rows.Next() {
var (
id int
username string
money int
)
_ = rows.Scan(&id, &username,&money)
fmt.Println(id, username,money)
}
}
複製代碼
添加數據庫記錄,可使用sql.DB
中的Exec()
或ExecContext()
方法,這兩個方法的定義以下:
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
複製代碼
代碼示例:
func insert(){
insertText := "INSERT INTO users values(?,?,?)"
rs,err := db.Exec(insertText,4,"juejin",1000)
if err != nil{
fmt.Println(err)
return
}
if id,_ := rs.LastInsertId();id > 0 {
fmt.Println("插入成功")
}
/*也能夠這樣判斷是否插入成功
if n,_ := rs.RowsAffected();n > 0 {
fmt.Println("插入成功")
}
*/
}
複製代碼
Exec()
或ExecContext()
方法的第一個返回值爲一個實現了sql.Result
接口的類型,sql.Result
的定義以下:
注意
LastInsertId()
方法只有在使用INSERT語句且數據表有自增id時纔有返回自增id值,不然返回0。
type Result interface {
LastInsertId() (int64, error)//使用insert向數據插入記錄,數據表有自增id時,該函數有返回值
RowsAffected() (int64, error)//表示影響的數據錶行數
}
複製代碼
咱們能夠用sql.Result
中的LastInsertId()
方法或RowsAffected()
來判斷SQL
語句是否執行成功。
除了使用sql.DB
中的Exec()
和ExecContext()
方法外,也可使用Prepare()
或PrepareContext()
返回sql.Stmt
結構體,再經過sql.Stmt
中的Exec()
方法向數據表寫入數據。
使用sql.Stmt
向數據表寫入數據的演示:
func insertStmt(){
stmt,err := db.Prepare("INSERT INTO users VALUES(?,?,?)")
defer stmt.Close()
if err != nil{
return
}
rs,err := stmt.Exec(5,"juejin",1000)
if id,_ := rs.LastInsertId(); id > 0 {
fmt.Println("插入成功")
}
}
複製代碼
注意,使用
sql.Stmt
中的Exec()
或ExecContext()
執行SQL
對更新和刪除語句一樣適合,下面講更新和刪除時再也不演示。
與往數據表裏添加數據同樣,可使用sql.DB
的Exec()
或ExecContext()
方法,不過,使用數據庫UPDATE
語句更新數據時,咱們只能經過sql.Result
結構體中的RowsAffected()
方法來判斷影響的數據行數,進而判斷是否執行成功。
func update() {
updateText := "UPDATE users SET username = ? WHERE id = ?"
rs,err := db.Exec(updateText,"database",2)
if err != nil{
fmt.Println(err)
return
}
if n,_ := rs.RowsAffected();n > 0 {
fmt.Println("更新成功")
}
}
複製代碼
使用DELETE
語句刪除數據表記錄的操做與上面的更新語句是同樣的,請看下面的演示:
func del() {
delText := "DELETE FROM users WHERE id = ?"
rs,err := db.Exec(delText,1)
if err != nil{
fmt.Println(err)
return
}
fmt.Println(rs.RowsAffected())
}
複製代碼
在前面的示例中,咱們都沒有開啓事務,若是沒有開啓事務,那麼默認會把提交的每一條SQL
語句都看成一個事務來處理,若是多條語句一塊兒執行,當其中某個語句執行錯誤,則前面已經執行的SQL
語句沒法回滾。
對於一些要求比較嚴格的業務邏輯來講(如訂單付款、用戶轉帳等),應該在同一個事務提交多條SQL
語句,避免發生執行出錯沒法回滾事務的狀況。
如何開啓一個新的事務?可使用sql.DB
結構體中的Begin()
或BeginTx()
方法,這兩個方法的定義以下:
func (db *DB) Begin() (*Tx, error)
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
複製代碼
BeginTx()
方法的第二個參數爲TxOptions
,其定義以下:
type TxOptions struct {
// Isolation is the transaction isolation level.
// If zero, the driver or database's default level is used. Isolation IsolationLevel ReadOnly bool } 複製代碼
TxOptions
的Isolation
字段用於定義事務的隔離級別,其類型爲IsolationLevel
,Ioslation
的取值範圍能夠爲以下常量:
const (
LevelDefault IsolationLevel = iota
LevelReadUncommitted
LevelReadCommitted
LevelWriteCommitted
LevelRepeatableRead
LevelSnapshot
LevelSerializable
LevelLinearizable
)
複製代碼
Begin()
和BeginTxt()
方法返回一個sql.Tx
結構體,使用sql.Tx
對數據庫進行操做,會在同一個事務中提交,下面演示代碼:
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
//Begin方法其實是調用BeginTx()方法,db.BeginTx(context.Background(), nil)
tx, err := db.Begin()
複製代碼
下面是sql.Tx
結構體中的基本操做方法,使用方式與咱們前面演示的例子同樣
func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
複製代碼
當使用sql.Tx
的操做方式操做數據後,須要咱們使用sql.Tx
的Commit()
方法顯式地提交事務,若是出錯,則可使用sql.Tx
中的Rollback()
方法回滾事務,保持數據的一致性,下面是這兩個方法的定義:
func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error
複製代碼
sql.Tx
結構體中的Stmt()
和StmtContext()
能夠將sql.Stmt
封裝爲支持事務的sql.Stmt
結構體並返回,這兩個方法的定義以下:
func (tx *Tx) Stmt(stmt *Stmt) *Stmt
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt
複製代碼
而使用sql.Tx
中的Prepare()
和PrepareContext()
方法則能夠直接返回一個支持事務的sql.Stmt
結構體
func (tx *Tx) Prepare(query string) (*Stmt, error)
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)
複製代碼
//修改
func txUpdate(){
tx,_ := db.Begin()
rs,err := tx.Exec("UPDATE users SET username = ? WHERE id = ?","sssss",2)
if err != nil{
panic(err)
}
err = tx.Commit()
if err != nil{
panic(err)
}
if n,_ := rs.RowsAffected();n > 0{
fmt.Println("成功")
}
}
//使用Stmt修改
func txStmt(){
tx,err := db.Begin()
if err != nil{
panic(err)
}
stmt,err := db.Prepare("UPDATE users SET username = ? WHERE id = ?")
stmtTx := tx.Stmt(stmt)
defer stmtTx.Close()
rs,_ := stmtTx.Exec("test",2)
_ = tx.Commit()
if n,_ := rs.RowsAffected();n > 0{
fmt.Println("成功")
}
}
複製代碼
前面咱們介紹是Go語言原生對數據庫編程的支持,不過,更方便的是,咱們能夠直接使用一些開源的ORM
(Object Relational Mapping
)框架,ORM
框架能夠封裝了底層的SQL
語句,並直接映射爲Struct
,Map
等數據類型,省去咱們直接寫SQL
語句的工做,很是簡單方便。
下面幾個比較經常使用的ORM框架:
GORM
是一個很是完善的ORM框架,除了基本增長改查的支持,也支持關聯包含一個,包含多個,屬於,多對多的數據表,另外也能夠在建立/保存/更新/刪除/查找以前或以後寫鉤子回調,同時也支持事務。
GORM目前支持數據庫驅動MySQL
、SQLite3
、SQL Server
、Postgres
。
Xorm
也是一個簡單又強大的ORM框架,其功能也GORM
是相似的,不過支持的數據庫驅動比GORM
多一些,支持MySQL
,SQL Server
,SQLite3
,Postgres
,MyMysql
,Tidb
,Oracle
等數據庫驅動。
Beego ORM
是國人開發的Web框架Beego
中的一個模塊,雖然是Beego
的一個模塊,但能夠獨立使用,不過目前Beego ORM
只支持MySQL
、SQLite3
,Postgres
等數據庫驅動。
除了上面咱們介紹的三個ORM框架,其實還不少很好的ORM的框架,你們有空能夠看看。
Go語言將對數據庫的操做抽象並封裝在sql/database
包中,爲咱們操做不一樣數據庫提供統一的API,很是實用,咱們在這篇文章中講解了sql/database
包中的sql.DB
,sql.Rows
,sql.Stmt
,sql.Tx
等結構體的使用,相信你經過上面的示例,也必定能掌握Go語言操做數據庫的方法。
你的關注,是我寫做路上最大的鼓勵!