作為服務端程序谈宛,對數據庫的訪問是很常見的操作。我們來熟悉一下
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)
}
}