《Go語(yǔ)言四十二章經(jīng)》第三十九章 Mysql數(shù)據(jù)庫(kù)

《Go語(yǔ)言四十二章經(jīng)》第三十九章 MySql數(shù)據(jù)庫(kù)

作者:李驍

39.1 database/sql包

Go 提供了database/sql包用于對(duì)關(guān)系型數(shù)據(jù)庫(kù)的訪問(wèn)切距,作為操作數(shù)據(jù)庫(kù)的入口對(duì)象sql.DB朽缎,主要為我們提供了兩個(gè)重要的功能:

  • sql.DB 通過(guò)數(shù)據(jù)庫(kù)驅(qū)動(dòng)為我們提供管理底層數(shù)據(jù)庫(kù)連接的打開(kāi)和關(guān)閉操作.
  • sql.DB 為我們管理數(shù)據(jù)庫(kù)連接池

需要注意的是,sql.DB表示操作數(shù)據(jù)庫(kù)的抽象訪問(wèn)接口, 而非一個(gè)數(shù)據(jù)庫(kù)連接對(duì)象;它可以根據(jù)driver打開(kāi)關(guān)閉數(shù)據(jù)庫(kù)連接谜悟,管理連接池话肖。正在使用的連接被標(biāo)記為繁忙,用完后回到連接池等待下次使用葡幸。所以最筒,如果你沒(méi)有把連接釋放回連接池,會(huì)導(dǎo)致過(guò)多連接使系統(tǒng)資源耗盡礼患。

具體到某一類(lèi)型的關(guān)系型數(shù)據(jù)庫(kù)是钥,需要導(dǎo)入對(duì)應(yīng)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)。下面以MySql8.0為例缅叠,來(lái)講講怎么在Go語(yǔ)言中調(diào)用悄泥。

首先,需要下載第三方包:

go get github.com/go-sql-driver/mysql

在代碼中導(dǎo)入mysql數(shù)據(jù)庫(kù)驅(qū)動(dòng):

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

通常來(lái)說(shuō)肤粱,不應(yīng)該直接使用驅(qū)動(dòng)所提供的方法弹囚,而是應(yīng)該使用 sql.DB,因此在導(dǎo)入 mysql 驅(qū)動(dòng)時(shí)领曼,這里使用了匿名導(dǎo)入的方式(在包路徑前添加 _)鸥鹉,當(dāng)導(dǎo)入了一個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)后,此驅(qū)動(dòng)會(huì)自行初始化并注冊(cè)自己到Go的database/sql上下文中庶骄,因此我們就可以通過(guò) database/sql 包提供的方法訪問(wèn)數(shù)據(jù)庫(kù)了毁渗。

39.2 Mysql數(shù)據(jù)庫(kù)操作

我們先建立表結(jié)構(gòu):

CREATE TABLE t_article_cate (
`cid` int(10) NOT NULL AUTO_INCREMENT, 
  `cname` varchar(60) NOT NULL, 
  `ename` varchar(100), 
  `cateimg` varchar(255), 
  `addtime` int(10) unsigned NOT NULL DEFAULT '0', 
  `publishtime` int(10) unsigned NOT NULL DEFAULT '0', 
  `scope` int(10) unsigned NOT NULL DEFAULT '10000', 
  `status` tinyint(1) unsigned NOT NULL DEFAULT '0', 
  PRIMARY KEY (`cid`), 
  UNIQUE  KEY catename (`cname`)
) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

由于預(yù)編譯語(yǔ)句(PreparedStatement)提供了諸多好處,可以實(shí)現(xiàn)自定義參數(shù)的查詢(xún)单刁,通常來(lái)說(shuō)灸异,比手動(dòng)拼接字符串 SQL 語(yǔ)句高效,可以防止SQL注入攻擊。

下面代碼使用預(yù)編譯的方式肺樟,來(lái)進(jìn)行增刪改查的操作檐春,并通過(guò)事務(wù)來(lái)批量提交一批數(shù)據(jù)。

在Go語(yǔ)言中對(duì)數(shù)據(jù)類(lèi)型要求很?chē)?yán)格么伯,一般查詢(xún)數(shù)據(jù)時(shí)先定義數(shù)據(jù)類(lèi)型疟暖,但是查詢(xún)數(shù)據(jù)庫(kù)中的數(shù)據(jù)存在三種可能:
存在值,存在零值田柔,未賦值NULL 三種狀態(tài)俐巴,因此可以將待查詢(xún)的數(shù)據(jù)類(lèi)型定義為sql.Nullxxx類(lèi)型,可以通過(guò)判斷Valid值來(lái)判斷查詢(xún)到的值是否為賦值狀態(tài)還是未賦值NULL狀態(tài)凯楔。如// sql.NullInt64 sql.NullString

package main

import (
    "database/sql"
    "fmt"
    "strings"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

type DbWorker struct {
    Dsn string
    Db  *sql.DB
}

type Cate struct {
    cid     int
    cname   string
    addtime int
    scope   int
}



func main() {
    dbw := DbWorker{Dsn: "root:123456@tcp(localhost:3306)/mydb?charset=utf8mb4"}
    // 支持下面幾種DSN寫(xiě)法窜骄,具體看mysql服務(wù)端配置,常見(jiàn)為第2種
    // user@unix(/path/to/socket)/dbname?charset=utf8
    // user:password@tcp(localhost:5555)/dbname?charset=utf8
    // user:password@/dbname
    // user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

    dbtemp,  err := sql.Open("mysql",  dbw.Dsn)
    dbw.Db = dbtemp

    if err != nil {
        panic(err)
        return
    }
    defer dbw.Db.Close()

    // 插入數(shù)據(jù)測(cè)試
    dbw.insertData()

    // 刪除數(shù)據(jù)測(cè)試
    dbw.deleteData()

    // 修改數(shù)據(jù)測(cè)試
    dbw.editData()

    // 查詢(xún)數(shù)據(jù)測(cè)試
    dbw.queryData()

    // 事務(wù)操作測(cè)試
    dbw.transaction()
}

每次db.Query操作后摆屯,都建議調(diào)用rows.Close()邻遏。 因?yàn)?db.Query() 會(huì)從數(shù)據(jù)庫(kù)連接池中獲取一個(gè)連接,這個(gè)底層連接在結(jié)果集(rows)未關(guān)閉前會(huì)被標(biāo)記為處于繁忙狀態(tài)虐骑。當(dāng)遍歷讀到最后一條記錄時(shí)准验,會(huì)發(fā)生一個(gè)內(nèi)部EOF錯(cuò)誤,自動(dòng)調(diào)用rows.Close(), 但如果提前退出循環(huán)廷没,rows不會(huì)關(guān)閉糊饱,連接不會(huì)回到連接池中,連接也不會(huì)關(guān)閉颠黎,則此連接會(huì)一直被占用另锋。 因此通常我們使用 defer rows.Close() 來(lái)確保數(shù)據(jù)庫(kù)連接可以正確放回到連接池中。

插入數(shù)據(jù):

// 插入數(shù)據(jù)狭归,sql預(yù)編譯
func (dbw *DbWorker) insertData() {
    stmt,  _ := dbw.Db.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
    defer stmt.Close()
    
    ret,  err := stmt.Exec("欄目1",  time.Now().Unix(),  10)

    // 通過(guò)返回的ret可以進(jìn)一步查詢(xún)本次插入數(shù)據(jù)影響的行數(shù)
    // RowsAffected和最后插入的Id(如果數(shù)據(jù)庫(kù)支持查詢(xún)最后插入Id)
    if err != nil {
        fmt.Printf("insert data error: %v\n",  err)
        return
    }
    if LastInsertId,  err := ret.LastInsertId(); nil == err {
        fmt.Println("LastInsertId:",  LastInsertId)
    }
    if RowsAffected,  err := ret.RowsAffected(); nil == err {
        fmt.Println("RowsAffected:",  RowsAffected)
    }
}

刪除數(shù)據(jù):

// 刪除數(shù)據(jù)夭坪,預(yù)編譯
func (dbw *DbWorker) deleteData() {
    stmt,  err := dbw.Db.Prepare(`DELETE FROM t_article_cate WHERE cid=?`)
    ret,  err := stmt.Exec(122)
    // 通過(guò)返回的ret可以進(jìn)一步查詢(xún)本次插入數(shù)據(jù)影響的行數(shù)RowsAffected和
    // 最后插入的Id(如果數(shù)據(jù)庫(kù)支持查詢(xún)最后插入Id).
    if err != nil {
        fmt.Printf("insert data error: %v\n",  err)
        return
    }
    if RowsAffected,  err := ret.RowsAffected(); nil == err {
        fmt.Println("RowsAffected:",  RowsAffected)
    }
}

修改數(shù)據(jù):

// 修改數(shù)據(jù),預(yù)編譯
func (dbw *DbWorker) editData() {
    stmt,  err := dbw.Db.Prepare(`UPDATE t_article_cate SET scope=? WHERE cid=?`)
    ret,  err := stmt.Exec(111,  123)
    // 通過(guò)返回的ret可以進(jìn)一步查詢(xún)本次插入數(shù)據(jù)影響的行數(shù)RowsAffected和
// 最后插入的Id(如果數(shù)據(jù)庫(kù)支持查詢(xún)最后插入Id).
    if err != nil {
        fmt.Printf("insert data error: %v\n",  err)
        return
    }
    if RowsAffected,  err := ret.RowsAffected(); nil == err {
        fmt.Println("RowsAffected:",  RowsAffected)
    }
}

查詢(xún)數(shù)據(jù):

// 查詢(xún)數(shù)據(jù)过椎,預(yù)編譯
func (dbw *DbWorker) queryData() {
    // 如果方法包含Query室梅,那么這個(gè)方法是用于查詢(xún)并返回rows的。其他用Exec()
// 另外一種寫(xiě)法
    // rows, err := db.Query("select id, name from users where id = ?", 1) 
    stmt,  _ := dbw.Db.Prepare(`SELECT cid, cname, addtime, scope From t_article_cate where status=?`)
    //err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) // 單行查詢(xún)疚宇,直接處理
    defer stmt.Close()

    rows,  err := stmt.Query(0)
    defer rows.Close()
    if err != nil {
        fmt.Printf("insert data error: %v\n",  err)
        return
    }

    // 構(gòu)造scanArgs亡鼠、values兩個(gè)slice,
// scanArgs的每個(gè)值指向values相應(yīng)值的地址
    columns,  _ := rows.Columns()
    fmt.Println(columns)
    rowMaps := make([]map[string]string,  9)
    values := make([]sql.RawBytes,  len(columns))
    scans := make([]interface{},  len(columns))
    for i := range values {
        scans[i] = &values[i]
        scans[i] = &values[i]
    }
    i := 0
    for rows.Next() {
        //將行數(shù)據(jù)保存到record字典
        err = rows.Scan(scans...)

        each := make(map[string]string,  4)
    // 由于是map引用敷待,放在上層for時(shí)间涵,rowMaps最終返回值是最后一條。
        for i,  col := range values {
            each[columns[i]] = string(col)
        }

// 切片追加數(shù)據(jù)榜揖,索引位置有意思浑厚。不這樣寫(xiě)就不是希望的樣子股耽。
        rowMaps = append(rowMaps[:i],  each) 
        fmt.Println(each)
        i++
    }
    fmt.Println(rowMaps)

    for i,  col := range rowMaps {
        fmt.Println(i,  col)
    }

    err = rows.Err()
    if err != nil {
        fmt.Printf(err.Error())
    }
}

事務(wù)處理:
db.Begin()開(kāi)始事務(wù)根盒,Commit() 或 Rollback()關(guān)閉事務(wù)钳幅。Tx從連接池中取出一個(gè)連接,在關(guān)閉之前都使用這個(gè)連接炎滞。Tx不能和DB層的BEGIN敢艰,COMMIT混合使用。

func (dbw *DbWorker) transaction() {
    tx,  err := dbw.Db.Begin()
    if err != nil {

        fmt.Printf("insert data error: %v\n",  err)
        return
    }
    defer tx.Rollback()
    stmt,  err := tx.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
    if err != nil {

        fmt.Printf("insert data error: %v\n",  err)
        return
    }

    for i := 100; i < 110; i++ {
        cname := strings.Join([]string{"欄目-",  string(i)},  "-")
        _,  err = stmt.Exec(cname,  time.Now().Unix(),  i+20)
        if err != nil {
            fmt.Printf("insert data error: %v\n",  err)
            return
        }
    }
    err = tx.Commit()
    if err != nil {
        fmt.Printf("insert data error: %v\n",  err)
        return
    }
    stmt.Close()
}

本書(shū)《Go語(yǔ)言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書(shū)《Go語(yǔ)言四十二章經(jīng)》內(nèi)容在簡(jiǎn)書(shū)同步地址: http://www.reibang.com/nb/29056963

雖然本書(shū)中例子都經(jīng)過(guò)實(shí)際運(yùn)行册赛,但難免出現(xiàn)錯(cuò)誤和不足之處钠导,煩請(qǐng)您指出;如有建議也歡迎交流森瘪。
聯(lián)系郵箱:roteman@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牡属,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子扼睬,更是在濱河造成了極大的恐慌逮栅,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窗宇,死亡現(xiàn)場(chǎng)離奇詭異措伐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)军俊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)侥加,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人粪躬,你說(shuō)我怎么就攤上這事担败。” “怎么了镰官?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵提前,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我朋魔,道長(zhǎng)岖研,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任警检,我火速辦了婚禮孙援,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扇雕。我一直安慰自己拓售,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著芯杀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扣甲。 梳的紋絲不亂的頭發(fā)上鸽凶,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天币砂,我揣著相機(jī)與錄音,去河邊找鬼玻侥。 笑死决摧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凑兰。 我是一名探鬼主播掌桩,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼姑食!你這毒婦竟也來(lái)了波岛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤音半,失蹤者是張志新(化名)和其女友劉穎则拷,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體祟剔,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隔躲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了物延。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣旱。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叛薯,靈堂內(nèi)的尸體忽然破棺而出浑吟,到底是詐尸還是另有隱情,我是刑警寧澤耗溜,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布组力,位于F島的核電站,受9級(jí)特大地震影響抖拴,放射性物質(zhì)發(fā)生泄漏燎字。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一阿宅、第九天 我趴在偏房一處隱蔽的房頂上張望候衍。 院中可真熱鬧,春花似錦洒放、人聲如沸蛉鹿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)妖异。三九已至惋戏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間他膳,已是汗流浹背响逢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矩乐,地道東北人龄句。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像散罕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傀蓉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354