天天看點

go gin學習記錄2

環境

環境: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) {

}
           

好的,插入操作已經完善了,那我們測試看一下

go gin學習記錄2

看這個傳回是發生異常了,沒有得到預期的結果,看一下終端什麼情況

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"
)
           

然後我們重新啟動,再測試看看

go gin學習記錄2

看傳回這次是成功了,那我們看下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,
	})
}
           

那我們跑一下看看是否符合預期

go gin學習記錄2

麻蛋,沒擷取到資料。

看這個樣子,應該是資料沒綁定到我們定義的struct上,檢查一下,确實是綁定的時候沒有按照scan的方式來,把這裡調整一下:

var user User
	err = row.Scan(&user.ID, &user.Birth, &user.Name)
	if err != nil {
		log.Println(err.Error())
	}
           

再試試看是不是解決了問題

go gin學習記錄2

看這傳回應該是解決了個鳥蛋。。。

看終端的輸出還有條資訊列印:

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())
		}
	}
           

來吧,見證奇迹的時刻到了!

go gin學習記錄2

這下子資料是有了,但是這個資料不太對,字段和值混亂了。

難道scan的時候必須按照表的字段順序來?

試試看

繼續調整scan代碼

var user User
	for row.Next() {
		err = row.Scan(&user.ID, &user.Name, &user.Birth)
		if err != nil {
			log.Println(err.Error())
		}
	}
           
go gin學習記錄2

這次結果正常了,終于符合我們的預期了。

還是對使用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,
	})
           

然後再運作測試看一看

go gin學習記錄2

好的,看傳回是正常的,那我們看看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操作都完成了實作。

今天就到這裡。