環境
環境:mac m1,go version 1.17.2, goland, mysql
昨天學習了安裝和基本的參數,路由使用,今天接着學習一下資料庫的操作。
建立資料庫
測試資料庫操作,需要先準備一個測試用的資料庫,那我們就來建一個測試用資料庫t_gin和資料表user:
mysql> create database t_gin;
Query OK, 1 row affected (0.01 sec)
mysql> use t_gin;
Database changed
mysql> create table user(
-> id int not null auto_increment,
-> name varchar(32) not null default "" comment "user name",
-> birth varchar(32) not null default "" comment "user birthday",
-> primary key(id)
-> )engine=innodb comment "user info record";
Query OK, 0 rows affected (0.05 sec)
mysql>
好了,測試用的資料庫和表已經建好了,接下來開始撸碼。
先從原生SQL開始。
安裝mysql驅動
要操作mysql,需要先裝一下驅動:
$ go get github.com/go-sql-driver/mysql
go get: added github.com/go-sql-driver/mysql v1.7.0
驅動安裝好了。
原生SQL-插入操作
想一下,基礎的資料庫操作包含插入insert、更新update以及使用最多的查詢select操作,而我們要操作的目标資料表是user,是以在controller/目錄下我們建立一個user.go檔案,用來存放對user表的操作。
代碼如下:
package controller
import (
"github.com/gin-gonic/gin"
)
type UserController struct {
}
type User struct {
ID int
Name string
Birth string
}
func (u *UserController) CreateUser(c *gin.Context) {
}
func (u *UserController) GetUserInfo(c *gin.Context) {
}
func (u *UserController) UpdateUserInfo(c *gin.Context) {
}
先把3個基礎操作的處理列出來,内容一會兒再具體填充。
handle現在有了,路由我們也要來定義一下,按照昨天學的路由分組,user相關的這幾個操作可以都歸到一個group下,就叫user好了。
在main.go中,新增一個路由組user,用來測試插入、擷取、更新操作,代碼如下:
user := r.Group("/user")
{
userCtrl := controller.UserController{}
user.POST("/createUser", userCtrl.CreateUser)
user.GET("/getUserInfo", userCtrl.GetUserInfo)
user.POST("/updateUserInfo", userCtrl.UpdateUserInfo)
}
好的,代碼架構現在有了。
由于是做測試,是以user表中隻有3個字段,id\name\age,而id又是自增的主鍵,那在插入、更新操作中,我們需要操作的就隻有name\age兩個字段。
先完善插入操作CreateUser:
package controller
import (
"database/sql"
"github.com/gin-gonic/gin"
"log"
"net/http"
)
type UserController struct {
}
type User struct {
ID int
Name string
Birth string
}
func (u *UserController) CreateUser(c *gin.Context) {
name := c.PostForm("name")
birth := c.PostForm("birth")
db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/t_gin?charset=utf8&parseTime=true")
if err != nil {
log.Println(err.Error())
c.JSON(http.StatusOK, gin.H{
"code": 1,
"data": false,
})
}
result, err := db.Exec("insert into user(name,birth) values(?,?)", name, birth)
if err != nil {
log.Println(err.Error())
c.JSON(http.StatusOK, gin.H{
"code": 1,
"data": false,
})
}
ret := make(map[string]interface{})
ret["id"], _ = result.LastInsertId()
ret["effectRows"], _ = result.RowsAffected()
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": ret,
})
}
func (u *UserController) GetUserInfo(c *gin.Context) {
}
func (u *UserController) UpdateUserInfo(c *gin.Context) {
}
好的,插入操作已經完善了,那我們測試看一下

看這個傳回是發生異常了,沒有得到預期的結果,看一下終端什麼情況
2023/02/14 15:51:26 sql: unknown driver "mysql" (forgotten import?)
2023/02/14 15:51:26 [Recovery] 2023/02/14 - 15:51:26 panic recovered:
POST /user/createUser HTTP/1.1
Host: localhost:8080
Accept: */*
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 268
Content-Type: multipart/form-data; boundary=--------------------------217154931077337827823686
Postman-Token: 4b35385e-4d83-4fec-8d50-ec6779f2cd53
User-Agent: PostmanRuntime/7.30.1
runtime error: invalid memory address or nil pointer dereference
看樣子是沒有引入mysql的驅動,加一下
import (
"database/sql"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"log"
"net/http"
)
然後我們重新啟動,再測試看看
看傳回這次是成功了,那我們看下mysql中是否已經有了資料
mysql> select * from user;
+----+-------+----------+
| id | name | birth |
+----+-------+----------+
| 1 | Alice | 2002-2-3 |
+----+-------+----------+
1 row in set (0.03 sec)
mysql>
好的,mysql中也有資料了,插入操作嘗試成功,符合預期結果。
原生SQL-擷取操作
資料有寫入,就會有擷取。而在日常工作中,擷取資料是使用頻次最高的操作,那麼接下來就嘗試完善查詢操作。
完善後的查詢操作代碼如下:
func (u *UserController) GetUserInfo(c *gin.Context) {
id := c.Query("id")
db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/t_gin?charset=utf8&parseTime=true")
if err != nil {
log.Println(err.Error())
c.JSON(http.StatusOK, gin.H{
"code": 1,
"data": false,
})
}
row, err := db.Query("select * from user where id=?", id)
if err != nil {
log.Println(err.Error())
}
var user User
row.Scan(&user)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": user,
})
}
那我們跑一下看看是否符合預期
麻蛋,沒擷取到資料。
看這個樣子,應該是資料沒綁定到我們定義的struct上,檢查一下,确實是綁定的時候沒有按照scan的方式來,把這裡調整一下:
var user User
err = row.Scan(&user.ID, &user.Birth, &user.Name)
if err != nil {
log.Println(err.Error())
}
再試試看是不是解決了問題
看這傳回應該是解決了個鳥蛋。。。
看終端的輸出還有條資訊列印:
2023/02/14 16:49:19 sql: Scan called without calling Next
[GIN] 2023/02/14 - 16:49:19 | 200 | 6.09775ms | ::1 | GET "/user/getUserInfo?id=1"
看這提示是說scan應該調用next,這單條記錄查詢也要用next嗎?好吧先修改一下看看。
将scan部分的代碼調整一下:
var user User
for row.Next() {
err = row.Scan(&user.ID, &user.Birth, &user.Name)
if err != nil {
log.Println(err.Error())
}
}
來吧,見證奇迹的時刻到了!
這下子資料是有了,但是這個資料不太對,字段和值混亂了。
難道scan的時候必須按照表的字段順序來?
試試看
繼續調整scan代碼
var user User
for row.Next() {
err = row.Scan(&user.ID, &user.Name, &user.Birth)
if err != nil {
log.Println(err.Error())
}
}
這次結果正常了,終于符合我們的預期了。
還是對使用scan.next有些疑惑,又翻了翻文檔,發現有一個QueryRow方法,我們調整一下代碼,看看是不是符合:
row := db.QueryRow("select * from user where id=?", id)
var user User
err = row.Scan(&user.ID, &user.Name, &user.Birth)
if err != nil {
log.Panic(err.Error())
}
運作之後發現是符合預期的。
QueryRow是查詢單行資料,擷取到的資料是一條,可能直接scan進行操作;
而Query擷取到的是資料集,資料集中可能包含0條或多條資料,是以使用Query方法查詢出來的資料,就得使用next方法去疊代出來。
那麼就好了解了,擷取全量資料的場景“select * from user"就必須使用Query來處理了,擷取單挑記錄可以走QueryRow。
原生SQL-更新操作
接下來就是更新了。
先完善一下更新操作的代碼,完善之後是這樣:
func (u *UserController) UpdateUserInfo(c *gin.Context) {
id := c.PostForm("id")
name := c.PostForm("name")
birth := c.PostForm("birth")
db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/t_gin?charset=utf8&parseTime=true")
if err != nil {
log.Println(err.Error())
c.JSON(http.StatusOK, gin.H{
"code": 1,
"data": false,
})
}
result, err := db.Exec("update user set name=?,birth=? where id=?", name, birth, id)
if err != nil {
log.Println(err.Error())
}
c.JSON(http.StatusOK, gin.H{
"code":0,
"data":result.RowsAffected(),
})
}
啟動一下進行測試,發現報了個錯誤
$ go run main.go
# t_gin/controller
controller/user.go:110:30: multiple-value result.RowsAffected() in single-value context
看提示是RowsAffected()傳回兩個值,不比對,我們調整一下
eff, _ := result.RowsAffected()
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": eff,
})
然後再運作測試看一看
好的,看傳回是正常的,那我們看看mysql中的資料是不是确實被更新了
mysql> select * from user;
+----+------+----------+
| id | name | birth |
+----+------+----------+
| 1 | lucy | 2002-5-1 |
+----+------+----------+
1 row in set (0.00 sec)
mysql>
确實被更新了,資料沒問題。
這樣,更新操作也測試通過了。
至此,插入、擷取、更新這3個最基本的資料庫操作,我們在gin架構中使用go的原生sql操作都完成了實作。
今天就到這裡。