1. 程式人生 > 資訊 >微信公佈四月朋友圈十大謠言,包括“5 月 1 日之後全國將關閉所有的遊戲”等

微信公佈四月朋友圈十大謠言,包括“5 月 1 日之後全國將關閉所有的遊戲”等

作為服務端程式,對資料庫的訪問是很常見的操作。我們來熟悉一下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 裡執行下面的 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 協議,資料庫 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)
    }
}

這樣每當需要對錯誤進行檢查的時候,就執行 check(err)

當 db 連結資料庫正常之後,我們可以執行 sql 查詢語句了。

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

然後利用 for 迴圈遍歷返回的結果。

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 字典中

//將資料儲存到 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()

養成好習慣,麻煩會很少。

看一下 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 迴圈記錄結果

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 %dn", imgUrl, id, createDate, state)
}

沒一次迴圈的時候,我們都可以按照我們想要的順序,取得所有的欄位值。唯一需要注意的是, 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 %dn", 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 %dn", imgUrl, id, createDate, state)
    }

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

修改 main 函式中的當前可執行函式為 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

這回每次的執行結果就穩定了。

插入

插入資料 insert() 函式,連結資料庫還是一樣的,而 db 呼叫的函式改成了 Prepare 。執行的 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()
}

執行後會打印出當前插入記錄的 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()
}

執行後,列印修改了的記錄條數。

刪除

而刪除函式的程式碼與修改函式的程式碼比較起來就只有 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()
}

執行刪除函式,最後打印出刪除的記錄條數。

總結

為方便檢視,給出完整程式碼。如果你想測試執行,每次需要執行哪個功能,請相應的在 main 函式中註釋掉其他不執行的函式。

完整程式碼示例

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 %dn", 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)
    }
}