环境
环境: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操作都完成了实现。
今天就到这里。