47. 訪問MySql數據庫實現增刪改查

作為服務端程序谈宛,對數據庫的訪問是很常見的操作。我們來熟悉一下
go 語言訪問 MySql 數據庫的基本操作(增刪改查)。
數據庫訪問需要用到標準庫 "database/sql" 和 mysql 的驅動 "github.com/go-sql-driver/mysql" 若贮。這兩個包都需要引用坑质。mysql 的驅動因為只是需要它的 init() 初始化妈经,所以需要采用下劃線引用的方式。

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

在訪問數據庫前宇挫,我們先在 MySql 里建好表并預先插入一些數據以便測試程序苛吱。請在 MySql 里執(zhí)行下面的 SQL 腳本。

-- ----------------------------
-- Table structure for announcement
-- ----------------------------
DROP TABLE IF EXISTS `announcement`;
CREATE TABLE `announcement` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `imgUrl` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `detailUrl` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `createDate` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
  `state` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

-- ----------------------------
-- Records of announcement
-- ----------------------------
INSERT INTO `announcement` VALUES ('1', '/visitshop/img/ann/ann1.jpg', null, '2016-07-20', '0');
INSERT INTO `announcement` VALUES ('2', '/visitshop//img/ann/ann1.jpg', null, '2016-07-20', '0');
INSERT INTO `announcement` VALUES ('3', '/visitshop//img/ann/ann1.jpg', null, '2016-07-20', '0');
INSERT INTO `announcement` VALUES ('4', '/visitshop//img/ann/ann1.jpg', null, '2016-07-20', '0');

我們將在主函數里調用增刪改查函數器瘪。先預設這四種操作的函數名

    query()    //查詢
    query2()  //查詢
    insert()    //插入
    update()  //修改
    remove()  //刪除

然后分別實現這幾個函數翠储。
首先是 query() 查詢數據绘雁。
要想訪問數據庫,首先要打開數據庫鏈接彰亥。這就需要用到datebase/sql Open函數咧七。

db, err := sql.Open("mysql", "root:@/shopvisit")

我們的數據庫鏈接是這個樣子的。為了簡化操作任斋,我的數據庫用戶 root 是沒有密碼的继阻。而正常的數據庫鏈接應該是這個樣子的

db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/shopvisit")

這個鏈接說明數據庫用戶 root 的密碼是 123456,使用 tcp 協(xié)議废酷,數據庫 ip 地址是 127.0.0.1瘟檩,使用 3306 端口作為通訊端口。當前使用的庫名是 shopvisit 澈蟆。
當然鏈接數據庫的方式其實是有好幾種的

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

有興趣的話墨辛,可以都試一試。
既然有了數據庫鏈接的語句趴俘,就要有錯誤檢查睹簇。而錯誤檢查會較為頻繁的出現(go 語言的特色)
所以寫一個函數來直接處理它。

func check(err error) {
    if err != nil{
        fmt.Println(err)
        panic(err)
    }
}

這樣每當需要對錯誤進行檢查的時候寥闪,就執(zhí)行 check(err)
當 db 鏈接數據庫正常之后太惠,我們可以執(zhí)行 sql 查詢語句了。

    rows, err := db.Query("SELECT * FROM shopvisit.announcement")
    check(err)

然后利用 for 循環(huán)遍歷返回的結果疲憋。

for rows.Next() {

在循環(huán)體內凿渊,我們先取得記錄的列(字段),把列名參數的值和列地址關聯缚柳。

        columns, _ := rows.Columns()

        scanArgs := make([]interface{}, len(columns))
        values := make([]interface{}, len(columns))

        for i := range values {
            scanArgs[i] = &values[i]
        }

再把數據保存到 record 字典中

        //將數據保存到 record 字典
        err = rows.Scan(scanArgs...)
        record := make(map[string]string)
        for i, col := range values {
            if col != nil {
                record[columns[i]] = string(col.([]byte))
            }
        }

打印記錄 fmt.Println(record) 之后埃脏,一定要記得釋放資源

rows.Close()

養(yǎng)成好習慣,麻煩會很少秋忙。
看一下 query() 的完整代碼

func query() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    rows, err := db.Query("SELECT * FROM shopvisit.announcement")
    check(err)

    for rows.Next() {
        columns, _ := rows.Columns()

        scanArgs := make([]interface{}, len(columns))
        values := make([]interface{}, len(columns))

        for i := range values {
            scanArgs[i] = &values[i]
        }

        //將數據保存到 record 字典
        err = rows.Scan(scanArgs...)
        record := make(map[string]string)
        for i, col := range values {
            if col != nil {
                record[columns[i]] = string(col.([]byte))
            }
        }
        fmt.Println(record)
    }
    rows.Close()

}

在 main 函數中彩掐,注釋掉其他函數,只留下 query()翰绊,運行d看結果佩谷。多運行幾次,比較每次運行結果监嗜。
第一次運行:

map[state:0 id:1 imgUrl:/visitshop/img/ann/ann1.jpg createDate:2016-07-20]
map[id:2 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]
map[id:3 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]
map[createDate:2016-07-20 state:0 id:4 imgUrl:/visitshop//img/ann/ann1.jpg]

第二次運行:

map[id:1 imgUrl:/visitshop/img/ann/ann1.jpg createDate:2016-07-20 state:0]
map[id:2 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]
map[id:3 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]
map[id:4 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]

第三次運行:

map[createDate:2016-07-20 state:0 id:1 imgUrl:/visitshop/img/ann/ann1.jpg]
map[id:2 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]
map[imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0 id:3]
map[id:4 imgUrl:/visitshop//img/ann/ann1.jpg createDate:2016-07-20 state:0]

為什么每次都不一樣呢?這是因為我們使用了 map 字典來保存列抡谐。map 是無序的裁奇,所以每次都是隨機的顯示順序。
這顯然不符合我們一般的結果需要麦撵,那么刽肠,我們來編寫 query2()溃肪。
仍然是鏈接數據庫的語句作為開端,再跟著是查詢語句音五。

    db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/shopvisit?charset=utf8")
    check(err)

    rows, err := db.Query("SELECT id,imgUrl,createDate,state FROM announcement")
    check(err)

既然已經查詢無錯了惫撰,那么直接 for 循環(huán)記錄結果

    for rows.Next(){
        var id int
        var state int
        var imgUrl string
        var createDate string
        //注意這里的Scan括號中的參數順序,和 SELECT 的字段順序要保持一致躺涝。
        if err := rows.Scan(&id,&imgUrl,&createDate,&state); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s id is %d on %s with state %d\n", imgUrl, id, createDate, state)
    }

沒一次循環(huán)的時候厨钻,我們都可以按照我們想要的順序,取得所有的字段值坚嗜。唯一需要注意的是夯膀, rows.Scan 的參數順序,需要和 select 語句的字段保持順序一致苍蔬。這里主要指的是數據類型诱建。參數名可以不同。
這里做個比較

db.Query("SELECT id,imgUrl,createDate,state FROM announcement")
rows.Scan(&id,&imgUrl,&createDate,&state);

&符號是取變量的地址碟绑,注意觀察變量的數據類型和 select 后面參數的數據類型必須是一致的俺猿。聲明變量時的順序無所謂,Scan調用變量時的順序要注意 select 參數的順序一致格仲。

        var id int
        var state int
        var imgUrl string
        var createDate string

然后可以按照你想要的順序打印輸出

fmt.Printf("%s id is %d on %s with state %d\n", imgUrl, id, createDate, state)

完整 query2() 函數代碼

func query() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    rows, err := db.Query("SELECT * FROM shopvisit.announcement")
    check(err)

    for rows.Next() {
        columns, _ := rows.Columns()

        scanArgs := make([]interface{}, len(columns))
        values := make([]interface{}, len(columns))

        for i := range values {
            scanArgs[i] = &values[i]
        }

        //將數據保存到 record 字典
        err = rows.Scan(scanArgs...)
        record := make(map[string]string)
        for i, col := range values {
            if col != nil {
                record[columns[i]] = string(col.([]byte))
            }
        }
        fmt.Println(record)
    }
    rows.Close()

}
func query2()  {
    fmt.Println("Query2")
    db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/shopvisit?charset=utf8")
    check(err)

    rows, err := db.Query("SELECT id,imgUrl,createDate,state FROM announcement")
    check(err)

    for rows.Next(){
        var id int
        var state int
        var imgUrl string
        var createDate string
        //注意這里的Scan括號中的參數順序押袍,和 SELECT 的字段順序要保持一致。
        if err := rows.Scan(&id,&imgUrl,&createDate,&state); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s id is %d on %s with state %d\n", imgUrl, id, createDate, state)
    }

    if err := rows.Err(); err != nil {
        log.Fatal(err)
    }
    rows.Close()
}

修改 main 函數中的當前可執(zhí)行函數為 query2()抓狭,運行結果如下

Query2
/visitshop/img/ann/ann1.jpg id is 1 on 2016-07-20 with state 0
/visitshop//img/ann/ann1.jpg id is 2 on 2016-07-20 with state 0
/visitshop//img/ann/ann1.jpg id is 3 on 2016-07-20 with state 0
/visitshop//img/ann/ann1.jpg id is 4 on 2016-07-20 with state 0

這回每次的運行結果就穩(wěn)定了伯病。
插入數據 insert() 函數,鏈接數據庫還是一樣的否过,而 db 調用的函數改成了 Prepare 午笛。執(zhí)行的 sql 語句需要這樣寫

    stmt, err := db.Prepare(`INSERT announcement (imgUrl, detailUrl, createDate, state) VALUES (?, ?, ?, ?)`)
    check(err)

插入的值,必須按照 sql 順序來插入

    res, err := stmt.Exec("/visitshop/img/ann/cofox1.png",nil,"2017-09-06",0)
    check(err)

返回剛插入的這條記錄的 id

    id, err := res.LastInsertId()
    check(err)

完整的 insert() 函數代碼

func insert()  {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    stmt, err := db.Prepare(`INSERT announcement (imgUrl, detailUrl, createDate, state) VALUES (?, ?, ?, ?)`)
    check(err)

    res, err := stmt.Exec("/visitshop/img/ann/cofox1.png",nil,"2017-09-06",0)
    check(err)

    id, err := res.LastInsertId()
    check(err)

    fmt.Println(id)
    stmt.Close()

}

執(zhí)行后會打印出當前插入記錄的 id
修改函數和插入函數結構類似苗桂, sql 語句不同

    stmt, err := db.Prepare("UPDATE announcement set imgUrl=?, detailUrl=?, createDate=?, state=? WHERE id=?")
    check(err)

那么參數語句也要增加個 id 值(注意 id 參數是你要修改的那條記錄的 id)

    res, err := stmt.Exec("/visitshop/img/ann/cofox2.png", nil, "2017-09-05", 1, 7)
    check(err)

修改的結果返回語句就也調用了不同的函數 res.RowsAffected

    num, err := res.RowsAffected()
    check(err)

完整的修改函數代碼

func update() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    stmt, err := db.Prepare("UPDATE announcement set imgUrl=?, detailUrl=?, createDate=?, state=? WHERE id=?")
    check(err)

    res, err := stmt.Exec("/visitshop/img/ann/cofox2.png", nil, "2017-09-05", 1, 7)
    check(err)

    num, err := res.RowsAffected()
    check(err)

    fmt.Println(num)
    stmt.Close()
}

執(zhí)行后药磺,打印修改了的記錄條數。
而刪除函數的代碼與修改函數的代碼比較起來就只有 sql 語句和參數的差別了煤伟。其他的完全一樣癌佩。
完整的刪除函數的代碼

func remove() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    stmt, err := db.Prepare("DELETE FROM announcement WHERE id=?")
    check(err)

    res, err := stmt.Exec(7)
    check(err)

    num, err := res.RowsAffected()
    check(err)

    fmt.Println(num)
    stmt.Close()

}

執(zhí)行刪除函數,最后打印出刪除的記錄條數便锨。
為方便查看围辙,給出完整代碼。如果你想測試運行放案,每次需要運行哪個功能姚建,請相應的在 main 函數中注釋掉其他不執(zhí)行的函數。
完整代碼示例

package main

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

func main() {
    query()
    //query2()
    //insert()
    //update()
    //remove()
}



//查詢數據
func query() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    rows, err := db.Query("SELECT * FROM shopvisit.announcement")
    check(err)

    for rows.Next() {
        columns, _ := rows.Columns()

        scanArgs := make([]interface{}, len(columns))
        values := make([]interface{}, len(columns))

        for i := range values {
            scanArgs[i] = &values[i]
        }

        //將數據保存到 record 字典
        err = rows.Scan(scanArgs...)
        record := make(map[string]string)
        for i, col := range values {
            if col != nil {
                record[columns[i]] = string(col.([]byte))
            }
        }
        fmt.Println(record)
    }
    rows.Close()

}
func query2()  {
    fmt.Println("Query2")
    db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/shopvisit?charset=utf8")
    check(err)

    rows, err := db.Query("SELECT id,imgUrl,createDate,state FROM announcement")
    check(err)

    for rows.Next(){
        var id int
        var state int
        var imgUrl string
        var createDate string
        //注意這里的Scan括號中的參數順序吱殉,和 SELECT 的字段順序要保持一致掸冤。
        if err := rows.Scan(&id,&imgUrl,&createDate,&state); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s id is %d on %s with state %d\n", imgUrl, id, createDate, state)
    }

    if err := rows.Err(); err != nil {
        log.Fatal(err)
    }
    rows.Close()
}


//插入數據
func insert()  {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    stmt, err := db.Prepare(`INSERT announcement (imgUrl, detailUrl, createDate, state) VALUES (?, ?, ?, ?)`)
    check(err)

    res, err := stmt.Exec("/visitshop/img/ann/cofox1.png",nil,"2017-09-06",0)
    check(err)

    id, err := res.LastInsertId()
    check(err)

    fmt.Println(id)
    stmt.Close()

}

//修改數據
func update() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    stmt, err := db.Prepare("UPDATE announcement set imgUrl=?, detailUrl=?, createDate=?, state=? WHERE id=?")
    check(err)

    res, err := stmt.Exec("/visitshop/img/ann/cofox2.png", nil, "2017-09-05", 1, 7)
    check(err)

    num, err := res.RowsAffected()
    check(err)

    fmt.Println(num)
    stmt.Close()
}

//刪除數據
func remove() {
    db, err := sql.Open("mysql", "root:@/shopvisit")
    check(err)

    stmt, err := db.Prepare("DELETE FROM announcement WHERE id=?")
    check(err)

    res, err := stmt.Exec(7)
    check(err)

    num, err := res.RowsAffected()
    check(err)

    fmt.Println(num)
    stmt.Close()

}

func check(err error) {
    if err != nil{
        fmt.Println(err)
        panic(err)
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末厘托,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子稿湿,更是在濱河造成了極大的恐慌铅匹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饺藤,死亡現場離奇詭異包斑,居然都是意外死亡,警方通過查閱死者的電腦和手機策精,發(fā)現死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門舰始,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咽袜,你說我怎么就攤上這事丸卷。” “怎么了询刹?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵谜嫉,是天一觀的道長。 經常有香客問我凹联,道長沐兰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任蔽挠,我火速辦了婚禮住闯,結果婚禮上,老公的妹妹穿的比我還像新娘澳淑。我一直安慰自己比原,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布杠巡。 她就那樣靜靜地躺著量窘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氢拥。 梳的紋絲不亂的頭發(fā)上蚌铜,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音嫩海,去河邊找鬼冬殃。 笑死,一個胖子當著我的面吹牛叁怪,可吹牛的內容都是我干的造壮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼骂束,長吁一口氣:“原來是場噩夢啊……” “哼耳璧!你這毒婦竟也來了?” 一聲冷哼從身側響起展箱,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤旨枯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后混驰,有當地人在樹林里發(fā)現了一具尸體攀隔,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年栖榨,在試婚紗的時候發(fā)現自己被綠了昆汹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡婴栽,死狀恐怖满粗,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情愚争,我是刑警寧澤映皆,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站轰枝,受9級特大地震影響捅彻,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鞍陨,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一步淹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诚撵,春花似錦缭裆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至韧衣,卻和暖如春盅藻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畅铭。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工氏淑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硕噩。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓假残,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辉懒,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容