Go使用數據庫


Go中提供了database包,database包下有sql.driver。該包用來定義操作數據庫的接口,這保證了無論使用哪種數據庫,操作都是相同的。但Go並沒有提供連接數據庫的driver,如果需要操作數據庫,需要使用第三方的driver包。

因此以mysql為例:

go get github.com/Go-SQL-Driver/MySQL

安裝成功之后導入方式如下:

import (
   "database/sql"
   "fmt"
   _ "github.com/go-sql-driver/mysql"
   "os"
)

Go雖然提供一些方法,但是並不會提供一些特有的方法,但是眾所周知會有些特別的方法需要交給數據庫的驅動去實現。

選擇使用匿名導入包,會讓被導入的包編譯到可執行文件中,通常來講,導入包就可以使用包中的數據和方法。但是對於數據操作來講,我們不應該直接使用導入的驅動包所提供的方法,而應該使用sql.DB對象所提供的統一的方法。因此在導入MySQL的區動時,使用匿名導入的方式。在導入一個數據庫驅動后,該驅動會自行初始化並注冊到Golang的database/sql上下文中,這樣就能使用database/sql包所提供的方法來訪問數據庫了。

一, 連接數據庫

sql中的Open()函數,原型如下所示。

func Open(driverName, dataSourceName string) (*DB, error)

driverName: 使用的驅動名,就是這個名字其實就是數據驅動注冊到database/sql時所需使用的名字。

dataSourceName:數據庫連接信息。它包含數據庫的用戶名、密碼、數據庫注漿機及其需要連接的數據名等信息

db, err := sql.Open("mysql", "用戶名:密碼@tcp(ip:端口)/數據庫?charset=utf8")

var db *sql.DB  // 創建連接對象
func init()  {
   db, _ := sql.Open("mysql", "disk:disk@tcp(127.0.0.1:3306)/fileserver?charset=utf8")
   db.SetMaxOpenConns(1000) // 同時連接數
   err := db.Ping()  // 測試連接是否成功
   if err != nil {
      fmt.Println("error to link sql:" + err.Error())
      os.Exit(1)
   }
}

如上面代碼所示:一般將數據連接的方法寫在init函數中,保證到爆時候立即被執行

二,數據的增刪改查

直接調用DB對象的Exec()方法如下所示:

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

通過db.Exec()插入數據,通過返回的err可知插入失敗的原因,通過返回的結果可以進一步查詢本次插入數據庫所影響的行數(RowsAffected)和最后插入的ID(如果數據庫支持查詢最后插入ID)。

Exec()方法使用方式如下:

result, err := db.Exec("INSERT INTO userinfo (username, departname, created) VALUES (?,?,?)","Steven", "北京", "2020-09-16")

預編譯語句(PreparedStatement)提供了諸多好處。PreparedStatement可以實現自定義參數的查詢,通常會比手動拼接字符SQL串高效;還可以防止SQL注入攻擊。因此一般情況下用PreparedStatement和Exec()完成對INSERT、UPDATE、DELETE操作。使用DB對象的Prepare()方法獲得預編譯對象stmt,然后調用Exec()方法,語句如下。

func (db *DB) Prepare(query string) (*Stmt, error)

使用如下:

stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")

result,err : stmt.Exec("zhouli", "開發部", "2020-05-16")

獲取影響數據庫的行數,可以根據該數值判斷是否操作(插入、刪除或修改)成功。

count, err := result.RowAffected()

// 注意SQL的注入攻擊
   stm, err := mydb.DBConn().Prepare("insert ignore into tab_file(`file_sha1`, `file_name`, `file_size`)" +
   "`file_addr`,`status`values(?,?,?,?,1)")
if err != nil {
   fmt.Println("Failed to Prepare statement, err:" + err.Error())
   return false
}
defer stm.Close()
ret, err := stm.Exec(filehash, filename, filesize,fileaddr)
if err != nil {
   fmt.Println(err.Error())
   return false
}
// 檢測受到更新影響的行數
if rf,err := ret.RowsAffected();nil == err {
   if rf <= 0 {
      fmt.Printf("插入失敗")
   }
   return true
}

三, 查詢數據

數據庫查詢步驟如下:

1, 調用db.Query()方法執行SQL語句,此方法返回一個Rows作為查詢結果。語法如下:

func (db *DB) Query(query string,args ...interface{}) (*Rows, error)

2, 將rows.Next()方法的返回值作為for循環的條件,迭代查詢數據,語法如下所示。

func (rs *Rows) Next() bool

3, 再循環中,通過rows.Scan()方法讀取每一行數據,語法如下所示:

func (rs *Rows) Scan(dest ...interface{}) error

4, 調用db.Close()關閉查詢

通過QueryRow()方法查詢單挑數據,語法如下所示:

func (db *DB) QueryRow(query string,args ...interface{}) *Row

整體步驟如下所示:

var username,departname, created string

err := db.QueryRow("SELECT username,departname, created FORM user_info WHERE uid=?",3).Scan(&username, &departname, &created)

查詢多行數據:

stmt, err := db.Prepare("SELECT * FORM user_info WHERE uid<?")

rows, err := stmt.Query(10)

user := new(UserTable)
for rows.Next() {
    err := row.Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
    if err != nil {
        panic(err)
        continue
    }
    fmt.Println(*user)
}

rows.Scan()方法參數的順序很重要,必須和查詢結果的column相對應(數量和順序都要相對應),不然會造成數據讀取的錯位

四, 注意點

每次db.Query()操作后,都建議調用rows.Close()

因為db.Query()會從數據庫連接池中獲取一個連接,這個底層連接在結果集rows未關閉前會被標記為繁忙狀態。當遍歷到最后一條記錄,會發生一個內部錯誤EOF錯誤,自動調用row.Close()。

但如果出現異常,提前退出循環,rows不會被關閉,連接不會回到連接池中,連接不會關閉,則詞里阿傑會一直被占用。因此通常使用defer rows.Close()來確保數據庫連接可以被正確的放回到連接池中。

但是我們從源碼中可以找到rows.Close()操作是冪等操作,而一個冪等操作最大的特點就是:其任意多次指定所產生的的影響與一次執行的影響相同。所以即便對已關閉的row再執行close也是沒事的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM