首页 > 数据库 >gin+MySQL简单实现数据库查询

gin+MySQL简单实现数据库查询

时间:2022-12-29 10:34:51浏览次数:52  
标签:name err 数据库 user MySQL gin id User

利用 gin 项目搭建一个简易的后端系统。

一个简易的 HTTP 响应接口

首先在 go 工作区的终端输入这条指令:

go get -u github.com/gin-gonic/gin

将 gin 项目的相关依赖保存到本地。

在终端生成 go mod 包管理文件:

go mod init

再创建一个 main.go 文件:

package main

import "github.com/gonic-gin/gin"

func main() {
    r := gin.Default()
    r.GET("/test", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "test",
        })
    })
    r.Run(":9999") // 运行在9999端口
}

因为我是在 wsl 运行这个项目,所以还需要先获取虚拟机的 ip,然后用 curl 测试:

curl http://<ip>:9999/ping

返回结果:

{"message":"test"}

连接 MySQL

关于 MySQL 操作这部分,一开始想着简单,只学了查询数据库部分,大多数踩得坑都是在查询部分,后面觉得要举一反三,就在原来基础上又写了添加的部分,在添加数据这一块写的不是很详细。

添加

先把基本的框架写出来,这里我连接的数据库结构体如下:

type SqlUser struct {
	User_id       string `json:"user_id" from:"user_id"`	
	User_name     string `json:"user_name" from:"user_name"`
}

添加单条数据:

Db, _ := sql.Open("mysql", "<username>:<password>@(localhost:3306)/<yourDatabase>")
router.POST("/add", func(c *gin.Context) {
    User_id := c.Request.FormValue("User_id")
	User_name := c.Request.FormValue("User_name")
    result, err := db.SqlDB.Exec("INSERT INTO t_user (user_id,user_name) VALUE(?,?)", u.User_id, u.User_name)
    if err != nil {
        log.Fatalln(err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        log.Fatalln(err)
    }
    msg := fmt.Sprintf("insert successfully %d", id)
    c.JSON(http.StatusOK, gin.H{
        "msg": msg,
        "user_name": User_name,
    })
})
r.Run(":9999")

执行非 query 操作,使用 Exec 方法,使用 curl 进行测试:

curl -X POST http://<ip>:9999/add -d "User_id=2&User_name=aaa"

返回结果如下:

{"msg":"insert successfully 1","user_name":"test"}
查询

单条查询如下

r.GET("/test/:id", func(c *gin.Context) {
    id: c.Param("id")
    var u SqlUser
    err := Db.QueryRow("select user_id,user_name from t_user where user_id =?", id).Scan(&u.user_id, &u.user_name)
    if err != nil {
        log.Fatalln(err)
        c.JSON(http.StatusOK, gin.H{
            "user": nil,
        })
        return
    }
    c.JSON(http.StatusOK, gin.H{
        "user": u.user_name,
    })
})

然后用 curl 命令去测试:

curl http://<ip>:9999/test/1

会得到之前在 MySQL 中预先保存的条目,样例结构如下:

{"user":"test"}

然而,最开始我不是这样写的,当时的我,没整理好这个框架,直接在单文件里封装函数,下面是我的错误示范:

var Db *sql.DB
func QueryData(c *gin.Context) {
    id := c.Param("id")
    var u SqlUser
    err := Db.QueryRow("select user_id,user_name from t_user where user_id =?", id).Scan(&u.user_id, &u.user_name)
    if err != nil {
        log.Fatalln(err)
        c.JSON(200, gin.H{
            "user": nil,
        })
        return
    }
    c.JSON(200, gin.H{
        "user": u.user_name,
    })
}

func main() {
    Db, _ = sql.Open("/*mysql link*/")
    // 略去了这个问题相关的代码
    r.GET("/test/:id", QueryData)
    r.Run(":9999")
}

然后就出现这样的错误:

runtime error: invalid memory address or nil pointer dereference

必应上的结果是:指针声明后未初始化就赋值。

这个 bug 困扰了我一整天,然后就开始了痛苦的 debug,main 函数的 router 应该是没问题的,接口测试正常,那问题应该是出在 QueryData 上,那只能一个个打印变量测试吧,测试到 Db 时,发现这个变量为 nil,然后紧接着抛出刚才提到的错误。好吧,这个错误我在之前也偶尔犯过,虽然在 main 中对其赋了值,但作用域不一样,所以还是无法传值,那只好去考虑下框架了。

修改

修改单条数据:

router.PUT("/person/:id", func(c *gin.Context) {
    cid := c.Param("id")
    id, err := strconv.Atoi(cid)
    person := Person{Id: id}
    err = c.Bind(&person)
    if err != nil {
        log.Fatalln(err)
    }
    stmt, err := Db.Prepare("UPDATE person SET user_name=? where user_id=?")
    
    if err != nil {
        log.Fatalln(err)
    }
    defer stmt.Close()
    rs, err := stmt.Exec(person.user_name, person.user_id)
    if err != nil {
        log.Fatalln(err)
    }
    ra, err := rs.RowsAffected()
    if err != nil {
        log.Fatalln(err)
    }
    msg := fmt.Sprintf("UPDATE person %d successful %d", person.user_id, ra)
    c.JSON(http.StatusOK, gin.H{
        "msg": msg,
    })
})

urlencode 方式更新:

curl -X PUT http://<ip>:9999/person/1 -d "user_id=1&user_name=temp"

json 方式更新:

curl -X PUT http://<ip>:9999/person/1 -H "Content-Type: application/json" -d '{"user_name":"temp"}'
删除
router.DELETE("person/:id", func(c *gin.Context) {
    cid := c.Param("id")
    id, err := strconv.Atoi(cid)
    if err != nil {
        log.Fatalln(err)
    }
    rs, err := db.Exec("DELETE FROM person from where user_id=?", id)
    if err != nil {
        log.Fatalln(err)
    }
    ra, err := rs.RowsAffected()
    if err != nil {
        log.Fatalln(err)
    }
    msg := fmt.Sprintf("DELETE person %d successful %d", id, ra)
    c.JSON(http.StatusOK, gin.H{
        "msg": msg,
    })
})

直接使用删除接口

curl -X "DELETE" http://<ip>:9999/person/1 

这里的 DELETE 参数必须在双引号内,不然不会被识别为 delete 请求。

搭建框架

参考了大佬的做法 [1],我也来创建我的目录:

ginExample tree
.
├── api
│   └── query.go
├── database
│   └── mysql.go
├── main.go
├── models
│   └── queryUser.go
├── router.go
├── go.mod
└── go.sum

这里的话以添加、查询功能为例,增删改同理,具体可以查看文末的参考链接。

api 存放 handler 函数,model 存放数据模型。

数据库处理
// mysql.go
package database

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

var SqlDB *sql.DB

func init() {
    var err error
	SqlDB, err = sql.Open("mysql", "root:jaydenmysql@(127.0.0.1:3306)/microtest")
	if err != nil {
		log.Fatalln(err.Error())
	}
	err = SqlDB.Ping()
	if err != nil {
		log.Fatalln(err.Error())
	}
}

因为在别的包会用到 SqlDB 这个变量,因此必须大写 (golang 只有大写开头变量才是 public 类变量)

数据 model 封装

抽离出 SqlUser 结构体以及对应的方法:

package models

import (
	db "ginExample/database"
	"log"
)

type SqlUser struct {
	User_id       string `json:"user_id" from:"user_id"`	
	User_name     string `json:"user_name" from:"user_name"`
}

func (u *SqlUser) GetUser(id string) SqlUser {
	err := db.SqlDB.QueryRow("select user_id,user_name from t_user where user_id =?", id).Scan(&u.User_id, &u.User_name)
	if err != nil {
		log.Println(err)
		u.User_name = "nil"
		return *u
	}
	return *u
}

func (u *SqlUser) AddUser() (id int64, err error) {
    result, err := db.SqlDB.Exec("INSERT INTO t_user (user_id,user_name) VALUE(?,?)", u.User_id, u.User_name)
    if err != nil {
		log.Fatalln(err)
		return
	}
	id, err = result.LastInsertId()
	if err != nil {
		log.Fatalln(err)
		return
	}
	fmt.Println(id)
	return id, err
}

func (u *SqlUser) UpdateUser() int64 {
    stmt, err := db.SqlDB.Prepare("UPDATE t_user SET user_name=? where user_id=?")
	if err != nil {
		log.Fatalln(err)
	}
	rs, err := stmt.Exec(u.User_name, u.User_id)
	if err != nil {
		log.Fatalln(err)
	}
	ra, err := rs.RowsAffected()
	if err != nil {
		log.Fatalln(err)
	}
	return ra
}

func (u *SqlUser) DeleteUser(id string) int64 {
	rs, err := db.SqlDB.Exec("DELETE FROM t_user where user_id=?", id)
	if err != nil {
		log.Fatalln(err)
	}
	ra, err := rs.RowsAffected()
	if err != nil {
		log.Fatalln(err)
	}
	return ra
}
handler

然后把具体的 handler 封装到 api 中,handler 操作数据库,因此会引用 model 包。

package api

import (
	"fmt"
	"net/http"

	. "ginExample/models"

	"github.com/gin-gonic/gin"
)

func IndexApi(c *gin.Context) {
	c.String(http.StatusOK, "It works")
}

func GetUserApi(c *gin.Context) {
	var u SqlUser
	id := c.Param("id")
	u = u.GetUser(id)
	c.JSON(200, gin.H{
		"user": u.User_name,
	})
}

func AddUserApi(c *gin.Context) {
	User_id := c.Request.FormValue("User_id")
	User_name := c.Request.FormValue("User_name")
    // 这里的大小写一定要对应上结构体内的变量名

	u := models.SqlUser{User_id: User_id, User_name: User_name}

	rows, err := u.AddUser()
	if err != nil {
		log.Fatalln(err)
	}
	msg := fmt.Sprintf("insert successfully %d\n", rows)
	c.JSON(200, gin.H{
		"msg":       msg,
		"user_name": User_name,
	})
}

func UpdateUserApi(c *gin.Context) {
	User_id := c.Request.FormValue("User_id")
	User_name := c.Request.FormValue("User_name")

	u := models.SqlUser{User_id: User_id, User_name: User_name}
	row := u.UpdateUser()
	msg := fmt.Sprintf("update successful %d", row)
	c.JSON(200, gin.H{
		"msg": msg,
	})
}

func DeleteUserApi(c *gin.Context) {
	var u models.SqlUser
	id := c.Param("id")
	row := u.DeleteUser(id)
	msg := fmt.Sprintf("DELETE user successful %d", row)
	c.JSON(200, gin.H{
		"msg": msg,
	})
}
router

最后就是把路由抽离出来:

//router.go
package main

import (
    "github.com/gin-gonic/gin"
    . "ginExample/api"
)

func initRouter() *gin.Engine {
    router := gin.Default()
    router.GET("/", IndexApi)
    router.GET("/query/:id", GetUserApi)
    router.POST("/add", api.AddUserApi)
	router.PUT("/update/:id", api.UpdateUserApi)
	router.DELETE("/delete/:id", api.DeleteUserApi)
    return router
}
app 入口

最后就是 main 的 app 入口,将路由导入,同时在 main 即将结束时,关闭全局数据库连接池。

package main

import db "ginExample/database"

func main() {
    defer db.SqlDB.Close()
    router := initRouter()
    router.Run(":9999")
}

这里运行项目的话,不能像之前简单地使用 go run main.go ,因为 main 包含 main.gorouter.go,因此要运行 go run *.go,如果最终编译二进制项目,则运行 go build -o app

测试结果和上面是一样的,至此,基本的访问、操作数据库功能实现。

参考链接


  1. Gin实战:Gin+Mysql简单的Restful风格的API - 简书 (jianshu.com) ↩︎

标签:name,err,数据库,user,MySQL,gin,id,User
From: https://www.cnblogs.com/jaydenchang/p/17011862.html

相关文章

  • MySQL-存储引擎架构
    MySQL是一种分层体系结构的关系数据库。一共有三层:客户(连接)层,Server层,存储引擎层。简单理解就是这三层架构。官网的解释在这里。(这个部分建议看8.0的文档,8.0文档补充了架......
  • CentOS7安装MySQL5.7
    先进入MySQLCommunityDownloads(https://dev.mysql.com/downloads/),选择使用红色红框标记的菜单MySQLCommunityServer因为我们这里示范安装的是MySQL5.7.38,所以进......
  • Linux安装MySQL 8.0.27
    cd/usr/localmkdirmysqlcdmysqlwgethttps://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.27-linux-glibc2.12-x86_64.tar.xz#下载可能需要几分钟tar......
  • MySQL杂谈
    重拾mysql,记点新知识1、关于外键约束,项目中一直少用外键约束,是怕删除的时候删除不掉,原来是可以设置的,关于外键约束有四种更新行为NOACTION:当在父表中删除/更新对应......
  • Windows安装MySQL-5.7.27
    一、确认本地是否安装mysql按win+r快捷键打开运行;输入services.msc,点击【确定】;在打开的服务列表中查找mysql服务,如果没有mysql服务,说明本机没有安装mysql,反之,说明......
  • 本地navicat连接不上云服务器的mysql
    最终排查是云服务器安装了宝塔,宝塔对端口又设置了一层防火墙规则(3306没有对外开放)。排查问题指南:https://blog.csdn.net/qq_40936395/article/details/127744040......
  • 解决mysql8.0连接时的:对实体 "serverTimezone" 的引用必须以 ';' 分隔符结尾
    原url:url="jdbc:mysql://localhost:3306/db_1?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";缺乏;在每个&后添加“amp;”,即转义为;修改后url:jdbc:mysql:/......
  • MySQL基础
    MySQL数据库(SQL语言)(一)MySQL概述数据库相关概念数据库(DataBase->DB):存储数据的仓库,数据是有组织的进行存储数据库管理系统(DataBaseManagementSystem->DBMS......
  • day46 mysql开始
    归来放假了前几天和对象出去玩了而且还阳了虚了一个多星期js学完准备一边看vue的课一边学习后端的知识安装mysql下载放置环境目录配置环境变量(由于之前安装......
  • 《李沐实用机器学习之5.2 Bagging》
    slides:https://c.d2l.ai/stanford-cs329p/_static/pdfs/cs329p_slides_7_2.pdf视频:https://www.bilibili.com/video/BV13g411N7xy/?spm_id_from=333.999.0.0&vd_source=......