47. 訪問MySql資料庫實現增刪改查 | 厚土Go學習筆記
作為服務端程式,對資料庫的訪問是很常見的操作。我們來熟悉一下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)
}
}