go gin学习记录2

Posted 梁吉林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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) 


好的,插入操作已经完善了,那我们测试看一下

看这个返回是发生异常了,没有得到预期的结果,看一下终端什么情况

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操作都完成了实现。

今天就到这里。

以上是关于go gin学习记录2的主要内容,如果未能解决你的问题,请参考以下文章

go gin学习记录2

go gin学习记录4

Go语言web框架 gin

go gin学习记录5

基于Go博客wblog的理解和修改。

学习go gin框架