之前的文章介绍了gin框架的基础使用,现代的web系统,很少不与数据库打交道的,接下来的几篇文章,从日常使用的比较多的数据库MySQL, Mongodb, redis 来分别简单的介绍如何使用,这篇先介绍一下MySQL的使用。

大多数的文章教程都是以GORM为主,但是我对于ORM实在不感兴趣,本文会以原生的sql语言进行CURD操作。

数据库的初始化

我使用的是https://github.com/go-sql-driver/mysql 这个库,在项目目录中创建一个db的目录,再创建一个initdb.go,我将数据库的初始化操作放到这个文件中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package db

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

func InitDB() *sql.DB {
	dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=%s&parseTime=true&loc=Local",
		"root", "123456", "127.0.0.1:3306", "gintest", "utf8")

	if conn, err := sql.Open("mysql", dsn); err != nil {
		panic(err.Error())
	} else {
		conn.SetConnMaxLifetime(7 * time.Second) //设置空闲时间,这个是比mysql 主动断开的时候短
		conn.SetMaxOpenConns(10)
		conn.SetMaxIdleConns(10)
		return conn
	}
}

sql.Open 方法开始连接数据库,第一个参数为所要连接的数据库类型。第二个参数为dsn,可以理解为连接数据库的参数信息。

这里在连接以后,又对连接进行了三个设置。

conn.SetConnMaxLifetime 为设置连接的空闲时间,为什么要设置这个参数以及该设置多少是有讲究的。

有的MySQL服务器为了性能考虑,会设置主动断开空闲连接的,默认8个小时,但是一般的dba不会设置那么长,很有可能会设置10秒或者更短,所以这个参数要设置的更短,这个参数可以登录MySQL 服务器执行show global variables like '%timeout%'; 来查看,有个wait_timeout 值,这里的值要设置比这个值更短。

conn.SetMaxOpenConns(10)conn.SetMaxOpenConns(10) 这两个方法会初始化一个连接池,保证池子里会有足够的连接,这个值要设置多少需要根据应用的并发情况。

但是SetMaxIdleConns 的值不要小于SetMaxOpenConns 设置的值,否则会导致频繁的创建连接。

官方的解释为

db.SetMaxIdleConns() is recommended to be set same to db.SetMaxOpenConns(). When it is smaller than SetMaxOpenConns(), connections can be opened and closed much more frequently than you expect

这里还有个需要注意的是,在import 时,有个_ "github.com/go-sql-driver/mysql" 这行代码不能缺,该代码会执行一会init方法,初始化一些变量,如果没有这行的话,会由于一些变量未被初始化而panic .

一般情况下,我们会将数据库连接对象放到一个全局的变量中,然后将不同的数据库操作封装到dao中。

在项目目录下再创建一个global目录,再创建一个global.go文件, 在该文件中定义一些需要全局使用的变量.

1
2
3
4
5
6
7
package global

import "database/sql"

var (
	Mysql *sql.DB
)

之后在main.go中进行mysql变量的初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"github.com/gin-gonic/gin"
	"yyxtest/db"
	"yyxtest/global"
	"yyxtest/handlers"
)

func main() {
	r := gin.Default()
	global.Mysql = db.InitDB()
	userv1_h := handlers.UserV1{}
	userv1 := r.Group("/user/v1")
	{
		userv1.GET("/check", userv1_h.CheckUsers)
	}
	r.Run(":8080")
}

global.Mysql = db.InitDB() 这行代码为初始化全局的Mysql 对象, 之后我们创建一个dao文件夹,再创建一个userdao.go文件,这里封装数据库的查询. 先写一个查询所有记录的函数。

事先准备一个表,并插入几条数据

image-20220109161913084

数据查询

在userdao.go中写入以下代码,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package dao

import "yyxtest/global"

type user struct {
	ID int `json:"id"`
	Name string `json:"name"`
	Age int `json:"age"`
}


func GetAllUsers() []user {
	rows, err :=global.Mysql.Query("select * from user_infos")
	if err != nil{
		return nil
	}
	var persons = make([]user, 0)
	for rows.Next() {
		var a user
		err := rows.Scan(&a.ID, &a.Name, &a.Age)
		if err != nil {
			return nil
		}
		persons = append(persons, a)
	}
	return persons
}


GetAllUsers 函数执行select * from user_infos, 并且将该查询结果返回[]user 的结构体, 接下来再编写路由处理类, userv1_h.CheckUsers

在handler 文件夹中再创建一个userv1.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package handlers

import (
	"github.com/gin-gonic/gin"
	"yyxtest/dao"
)

type UserV1 struct {}

func (UserV1) CheckUsers(c *gin.Context)  {
	users := dao.GetAllUsers()
	if users == nil{
		c.JSON(200, gin.H{"error": 1, "msg": "查询失败"})
	}
	c.JSON(200, gin.H{"error":0, "msg":"查询成功", "users": users})
}

运行服务,如果没有什么意外的话将可以正常的请求到数据

image-20220109170809807

关于MySQL的查询相关的语句可以参考我之前的文章 golang查询MySQL数据,

SQL注入的问题

如果在进行数据库的查询时,没有对用户的输入进校验,而是完全的进行字符串拼接,将会导致SQL注入的问题发生,如以下的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func GetUser(name string) (user, error)  {
	sqlstr := fmt.Sprintf("select * from user_infos where `name`='%s'", name)
	row := global.Mysql.QueryRow(sqlstr)
	var u user
	err := row.Scan(&u.ID, &u.Name, &u.Age)
	if err != nil {
		return user{}, err
	}
	return u, nil
}

sqlstr 直接进行字符串拼接将会导致注入问题。

正确的作法应该是使用点位符

1
2
3
4
5
6
7
8
9
func GetUser(name string) (user, error)  {
	row := global.Mysql.QueryRow("select * from user_infos where name=?", name)
	var u user
	err := row.Scan(&u.ID, &u.Name, &u.Age)
	if err != nil {
		return user{}, err
	}
	return u, nil
}

插入数据

插入数据也比较简单,使用Exec函数 ,在userdao.go中写入以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func InsertUser(name string, age int) error  {
	exec, err := global.Mysql.Exec("INSERT into user_infos(name, age) values (?,?)", name, age)
	if err != nil {
		return err
	}
	_, err = exec.LastInsertId()
	if err != nil {
		return  err
	}
	return nil
}

在处理类中写入以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func (UserV1) AddUser(c *gin.Context)  {
	name := c.Query("name")
	age := c.Query("age")
	age_i, err:=strconv.Atoi(age)
	if err!=nil{
		c.JSON(200, gin.H{"error": 1, "msg": "age 参数不正确"})
	}else{
		err = dao.InsertUser(name, age_i)
		if err!= nil{
			c.JSON(200, gin.H{"error": 2, "msg": err.Error()})
		}else{
			c.JSON(200, gin.H{"error": 1, "msg": "插入成功"})
		}
	}

}

MySQL的初步使用还是比较简单的,只是在初始化的时候定义好相应的对象,但是MySQL是个博大精深的数据库,入门很简单,但是想要深入还是需要很多的内功修炼的。