簡單聊聊Golang數據庫編程的那些事

原創做者,公衆號【程序員讀書】,歡迎關注公衆號,轉載文章請註明出處哦。mysql

應該說,數據庫編程是任何編程語言都有提供的基礎功能模塊,不管是編程語言內置的支持,仍是經過外部庫來實現;固然啦,不一樣編程語言提供的數據庫編程API是不盡相同的,並且須要支持的數據庫也是多種多樣,如經常使用的MySQLSQLServer,Postgres等數據庫。git

拋開其餘編程語言不談,在這篇文章中,咱們就來聊一聊Go語言數據庫編程的那些事,瞭解如何使用Go語言提供的標準庫,編寫通用的操做數據庫的代碼。程序員

數據庫鏈接與驅動

database/sql和database/sql/driver

標準庫database/sql是Go語言的數據庫操做抽象層,具體的數據庫操做邏輯實現則由不一樣的第三方包來作,而標準庫database/sql/driver提供了這些第三方包實現的標準規範。github

因此,Go語言的數據庫編程,通常只須要導入標準庫database/sql包,這個包提供了操做數據庫全部必要的結構體、函數與方法,下面是導入這個包的語句:golang

import database/sql
複製代碼

Go語言這樣作的好處就是,當從一個數據庫遷移到另外一個數據庫時(如SQL Server遷移到MySQL),則只須要換一個驅動包即可以了。sql

Go支持的數據庫驅動包

前面咱們說Go語言數據操做的由不一樣第三方包來實現,那麼若是咱們想要鏈接MySQL數據庫的話,要怎麼實現一個這樣的包呢?實際上,Go語言標準庫database/sql/driver定義了實現第三方驅動包的全部接口,咱們只導入實現了database/sql/driver相關接口驅動包就能夠了。數據庫

下面是支持Golang的數據庫驅動列表:編程

github.com/golang/go/w…bash

安裝第三方驅動包

以MySQL數據庫驅動包爲例:服務器

$ go get -u github.com/go-sql-driver/mysql
複製代碼

導入驅動包

import database/sql
import _ "github.com/go-sql-driver/mysql"
複製代碼

sql.DB結構體

sql.DB結構是sql/database包封裝的一個數據庫操做對象,包含了操做數據庫的基本方法。

DSN

DSN全稱爲Data Source Name,表示數據庫連來源,用於定義如何鏈接數據庫,不一樣數據庫的DSN格式是不一樣的,這取決於數據庫驅動的實現,下面是go-sql-driver/sql的DSN格式,以下所示:

//[用戶名[:密碼]@][協議(數據庫服務器地址)]]/數據庫名稱?參數列表
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
複製代碼

初始化sql.DB

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.DBsql/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.RowsNext()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
複製代碼

QueryRowQueryRowContext返回一個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.DBExec()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 } 複製代碼

TxOptionsIsolation字段用於定義事務的隔離級別,其類型爲IsolationLevelIoslation的取值範圍能夠爲以下常量:

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支持的基本操做

下面是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.TxCommit()方法顯式地提交事務,若是出錯,則可使用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("成功")
    }
}
複製代碼

相關ORM框架

前面咱們介紹是Go語言原生對數據庫編程的支持,不過,更方便的是,咱們能夠直接使用一些開源的ORM(Object Relational Mapping)框架,ORM框架能夠封裝了底層的SQL語句,並直接映射爲Struct,Map等數據類型,省去咱們直接寫SQL語句的工做,很是簡單方便。

下面幾個比較經常使用的ORM框架:

GORM

GORM是一個很是完善的ORM框架,除了基本增長改查的支持,也支持關聯包含一個,包含多個,屬於,多對多的數據表,另外也能夠在建立/保存/更新/刪除/查找以前或以後寫鉤子回調,同時也支持事務。

GORM目前支持數據庫驅動MySQLSQLite3SQL ServerPostgres

Xorm

Xorm也是一個簡單又強大的ORM框架,其功能也GORM是相似的,不過支持的數據庫驅動比GORM多一些,支持MySQL,SQL Server,SQLite3,Postgres,MyMysql,Tidb,Oracle等數據庫驅動。

Beego ORM

Beego ORM是國人開發的Web框架Beego中的一個模塊,雖然是Beego的一個模塊,但能夠獨立使用,不過目前Beego ORM只支持MySQLSQLite3,Postgres等數據庫驅動。

除了上面咱們介紹的三個ORM框架,其實還不少很好的ORM的框架,你們有空能夠看看。

小結

Go語言將對數據庫的操做抽象並封裝在sql/database包中,爲咱們操做不一樣數據庫提供統一的API,很是實用,咱們在這篇文章中講解了sql/database包中的sql.DB,sql.Rows,sql.Stmt,sql.Tx等結構體的使用,相信你經過上面的示例,也必定能掌握Go語言操做數據庫的方法。


你的關注,是我寫做路上最大的鼓勵!

相關文章
相關標籤/搜索