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) //設(shè)置空閑時(shí)間,這個(gè)是比mysql 主動(dòng)斷開(kāi)的時(shí)候短
conn.SetMaxOpenConns(10)
conn.SetMaxIdleConns(10)
return conn
}
}

sql.Open 方法開(kāi)始連接數(shù)據(jù)庫(kù),第一個(gè)參數(shù)為所要連接的數(shù)據(jù)庫(kù)類型。第二個(gè)參數(shù)為dsn,可以理解為連接數(shù)據(jù)庫(kù)的參數(shù)信息。

這里在連接以后,又對(duì)連接進(jìn)行了三個(gè)設(shè)置。

conn.SetConnMaxLifetime 為設(shè)置連接的空閑時(shí)間,為什么要設(shè)置這個(gè)參數(shù)以及該設(shè)置多少是有講究的。

有的MySQL服務(wù)器為了性能考慮,會(huì)設(shè)置主動(dòng)斷開(kāi)空閑連接的,默認(rèn)8個(gè)小時(shí),但是一般的dba不會(huì)設(shè)置那么長(zhǎng),很有可能會(huì)設(shè)置10秒或者更短,所以這個(gè)參數(shù)要設(shè)置的更短,這個(gè)參數(shù)可以登錄MySQL 服務(wù)器執(zhí)行show global variables like '%timeout%'; 來(lái)查看,有個(gè)wait_timeout 值,這里的值要設(shè)置比這個(gè)值更短。

conn.SetMaxOpenConns(10) 和conn.SetMaxOpenConns(10) 這兩個(gè)方法會(huì)初始化一個(gè)連接池,保證池子里會(huì)有足夠的連接,這個(gè)值要設(shè)置多少需要根據(jù)應(yīng)用的并發(fā)情況。

但是SetMaxIdleConns 的值不要小于SetMaxOpenConns 設(shè)置的值,否則會(huì)導(dǎo)致頻繁的創(chuàng)建連接。

官方的解釋為

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

這里還有個(gè)需要注意的是,在import 時(shí),有個(gè)_ "github.com/go-sql-driver/mysql" 這行代碼不能缺,該代碼會(huì)執(zhí)行一下init方法,初始化一些變量,如果沒(méi)有這行的話,會(huì)由于一些變量未被初始化而panic.

一般情況下,我們會(huì)將數(shù)據(jù)庫(kù)連接對(duì)象放到一個(gè)全局的變量中,然后將不同的數(shù)據(jù)庫(kù)操作封裝到dao中。

在項(xiàng)目目錄下再創(chuàng)建一個(gè)global目錄,再創(chuàng)建一個(gè)global.go文件, 在該文件中定義一些需要全局使用的變量.

package global

import "database/sql"

var (
Mysql *sql.DB
)

之后在main.go中進(jìn)行mysql變量的初始化

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 對(duì)象, 之后我們創(chuàng)建一個(gè)dao文件夾,再創(chuàng)建一個(gè)userdao.go文件,這里封裝數(shù)據(jù)庫(kù)的查詢. 先寫(xiě)一個(gè)查詢所有記錄的函數(shù)。

事先準(zhǔn)備一個(gè)表,并插入幾條數(shù)據(jù)

idnameage
1楊彥星18
2飯團(tuán)兒1
3aaa100
4bbb22
5ccc19
635
746

數(shù)據(jù)查詢

在userdao.go中寫(xiě)入以下代碼,

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 函數(shù)執(zhí)行select * from user_infos, 并且將該查詢結(jié)果返回[]user 的結(jié)構(gòu)體, 接下來(lái)再編寫(xiě)路由處理類, userv1_h.CheckUsers

在handler 文件夾中再創(chuàng)建一個(gè)userv1.go

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

運(yùn)行服務(wù),如果沒(méi)有什么意外的話將可以正常的請(qǐng)求到數(shù)據(jù)

SQL注入的問(wèn)題

如果在進(jìn)行數(shù)據(jù)庫(kù)的查詢時(shí),沒(méi)有對(duì)用戶的輸入進(jìn)校驗(yàn),而是完全的進(jìn)行字符串拼接,將會(huì)導(dǎo)致SQL注入的問(wèn)題發(fā)生,如以下的代碼

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 直接進(jìn)行字符串拼接將會(huì)導(dǎo)致注入問(wèn)題。

正確的做法應(yīng)該是使用點(diǎn)位符

插入數(shù)據(jù)

插入數(shù)據(jù)也比較簡(jiǎn)單,使用Exec函數(shù) ,在userdao.go中寫(xiě)入以下代碼

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
}

在處理類中寫(xiě)入以下代碼

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 參數(shù)不正確"})
}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的初步使用還是比較簡(jiǎn)單的,只是在初始化的時(shí)候定義好相應(yīng)的對(duì)象,但是MySQL是個(gè)博大精深的數(shù)據(jù)庫(kù),入門(mén)很簡(jiǎn)單,但是想要深入還是需要很多的內(nèi)功修煉的,之后再深入一些事務(wù)相關(guān)的查詢。

文章轉(zhuǎn)自微信公眾號(hào)@序語(yǔ)程言

上一篇:

在 .NET 和 Python 中創(chuàng)建了相同的 API — 哪個(gè)性能更好

下一篇:

使用FastAPI與aiohttp進(jìn)行SSE響應(yīng)開(kāi)發(fā)
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門(mén)場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)