Gin+Gorm實現(xiàn)CRUD的實戰(zhàn)
簡介:
Q:Gin和Gorm都是干什么的?有什么區(qū)別?
A:Gin 和 Gorm 是 Go 編程語言中流行的開源庫。但是,它們服務于不同的目的,通常在 web 開發(fā)項目中一起使用。
Gin 是一個用于構建 HTTP 服務器的 web 框架。它提供了一個簡單易用的 API,用于處理 HTTP 請求和響應、路由、中間件和其他常見的 web 應用程序所需的功能。它以其高性能和簡約為特點,提供了輕量級和靈活的解決方案來構建 web 服務器。
Gorm 是 Go 的一個 ORM(對象關系映射)庫。它提供了一個簡單易用的 API,用于與數(shù)據(jù)庫交互、處理數(shù)據(jù)庫遷移和執(zhí)行常見的數(shù)據(jù)庫操作,如查詢、插入、更新和刪除記錄。它支持多種數(shù)據(jù)庫后端,包括 MySQL、PostgreSQL、SQLite 等。
總而言之, Gin 是用于處理 HTTP 請求和響應、路由、中間件和其他與網(wǎng)絡相關的東西的 web 框架,而 Gorm 則是用于與數(shù)據(jù)庫交互并執(zhí)行常見數(shù)據(jù)庫操作的 ORM 庫。它們通常一起使用,來處理 HTTP 請求/響應并在 web 開發(fā)項目中存儲或獲取數(shù)據(jù)。
開發(fā)環(huán)境:
- Windows 10
- VSCode
一、Gin
0. 快速入門:
package main
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
"github.com/thinkerou/favicon"
)
// 中間件(攔截器),功能:預處理,登錄授權、驗證、分頁、耗時統(tǒng)計...
// func myHandler() gin.HandlerFunc {
// return func(ctx *gin.Context) {
// // 通過自定義中間件,設置的值,在后續(xù)處理只要調用了這個中間件的都可以拿到這里的參數(shù)
// ctx.Set("usersesion", "userid-1")
// ctx.Next() // 放行
// ctx.Abort() // 阻止
// }
// }
func main() {
// 創(chuàng)建一個服務
ginServer := gin.Default()
ginServer.Use(favicon.New("./Arctime.ico")) // 這里如果添加了東西然后再運行沒有變化,請重啟瀏覽器,瀏覽器有緩存
// 加載靜態(tài)頁面
ginServer.LoadHTMLGlob("templates/*") // 一種是全局加載,一種是加載指定的文件
// 加載資源文件
ginServer.Static("/static", "./static")
// 相應一個頁面給前端
ginServer.GET("/index", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "index.html", gin.H{
"msg": "This data is come from Go background.",
})
})
// 能加載靜態(tài)頁面也可以加載測試文件
// 獲取請求中的參數(shù)
// 傳統(tǒng)方式:usl?userid=xxx&username=conqueror712
// Rustful方式:/user/info/1/conqueror712
// 下面是傳統(tǒng)方式的例子
ginServer.GET("/user/info", func(context *gin.Context) { // 這個格式是固定的
userid := context.Query("userid")
username := context.Query("username")
// 拿到之后返回給前端
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
// 此時執(zhí)行代碼之后,在瀏覽器中可以輸入http://localhost:8081/user/info?userid=111&username=666
// 就可以看到返回了JSON格式的數(shù)據(jù)
// 下面是Rustful方式的例子
ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) {
userid := context.Param("userid")
username := context.Param("username")
// 還是一樣,返回給前端
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
// 指定代碼后,只需要在瀏覽器中http://localhost:8081/user/info/111/555
// 就可以看到返回了JSON數(shù)據(jù)了,非常方便簡潔
// 序列化
// 前端給后端傳遞JSON
ginServer.POST("/json", func(ctx *gin.Context) {
// request.body
data, _ := ctx.GetRawData()
var m map[string]interface{} // Go語言中object一般用空接口來表示,可以接收anything
// 順帶一提,1.18以上,interface可以直接改成any
_ = json.Unmarshal(data, &m)
ctx.JSON(http.StatusOK, m)
})
// 用apipost或者postman寫一段json傳到localhost:8081/json里就可以了
/*
json示例:
{
"name": "Conqueror712",
"age": 666,
"address": "Mars"
}
*/
// 看到后端的實時響應里面接收到數(shù)據(jù)就可以了
// 處理表單請求 這些都是支持函數(shù)式編程,Go語言特性,可以把函數(shù)作為參數(shù)傳進來
ginServer.POST("/user/add", func(ctx *gin.Context) {
username := ctx.PostForm("username")
password := ctx.PostForm("password")
ctx.JSON(http.StatusOK, gin.H{
"msg": "ok",
"username": username,
"password": password,
})
})
// 路由
ginServer.GET("/test", func(ctx *gin.Context) {
// 重定向 -> 301
ctx.Redirect(301, "https://conqueror712.gitee.io/conqueror712.gitee.io/")
})
// http://localhost:8081/test
// 404
ginServer.NoRoute(func(ctx *gin.Context) {
ctx.HTML(404, "404.html", nil)
})
// 路由組暫略
// 服務器端口,用服務器端口來訪問地址
ginServer.Run(":8081") // 不寫的話默認是8080,也可以更改
}
API用法示例:https://gin-gonic.com/zh-cn/docs/examples/
1. 基準測試
Q:基準測試是什么?
A:基準測試,也稱為性能測試或壓力測試,是一種用于測量系統(tǒng)或組件性能的測試。基準測試的目的是了解系統(tǒng)或組件在特定條件下的性能,并將結果與其他類似系統(tǒng)或組件進行比較?;鶞蕼y試可用于評估各種類型的系統(tǒng)和組件,包括硬件、軟件、網(wǎng)絡和整個系統(tǒng)。
Q:什么時候需要基準測試呀?
A:基準測試通常涉及在被測系統(tǒng)或組件上運行特定工作負載或任務,并測量吞吐量、延遲時間、CPU使用率、內存使用率等各種性能指標?;鶞蕼y試的結果可用于識別瓶頸和性能問題,并做出有關如何優(yōu)化系統(tǒng)或組件以提高性能的明智決策。
有許多不同類型的基準測試,每種類型都有自己的指標和工作負載。常見的基準測試類型包括:
- 人工基準測試:使用人工工作負載來測量系統(tǒng)或組件的性能。
- 真實世界基準測試:使用真實世界的工作負載或場景來測量系統(tǒng)或組件的性能。
- 壓力測試:旨在將系統(tǒng)或組件推到極限,以確定在正常使用條件下可能不明顯的性能問題
重要的是要知道基準測試不是一次性的活動,而是應該定期進行的活動,以評估系統(tǒng)的性能并檢測隨時間的消耗。
Q:什么樣的基準測試結果是我們想要的呀?
A:
- 在一定的時間內實現(xiàn)的總調用數(shù),越高越好
- 單次操作耗時(ns/op),越低越好
- 堆內存分配 (B/op), 越低越好
- 每次操作的平均內存分配次數(shù)(allocs/op),越低越好
2. Gin的特性與Jsoniter:
Gin v1 穩(wěn)定的特性:
- 零分配路由。
- 仍然是最快的 http 路由器和框架。
- 完整的單元測試支持。
- 實戰(zhàn)考驗。
- API 凍結,新版本的發(fā)布不會破壞你的代碼。
- Gin 項目可以輕松部署在任何云提供商上。
Gin 使用 encoding/json 作為默認的 json 包,但是你可以在編譯中使用標簽將其修改為 jsoniter。
$ go build -tags=jsoniter .
Jsoniter是什么?
json-iterator是一款快且靈活的JSON解析器,同時提供Java和Go兩個版本。json-iterator是最快的JSON解析器。它最多能比普通的解析器快10倍之多- 獨特的
iterator api能夠直接遍歷JSON,極致性能、零內存分配 - 從dsljson和jsonparser借鑒了大量代碼。
下載依賴:go get github.com/json-iterator/go
二、GORM
0. 特性與安裝:
- 全功能 ORM
- 關聯(lián) (Has One,Has Many,Belongs To,Many To Many,多態(tài),單表繼承)
- Create,Save,Update,Delete,F(xiàn)ind 中鉤子方法
- 支持
Preload、Joins的預加載 - 事務,嵌套事務,Save Point,Rollback To Saved Point
- Context、預編譯模式、DryRun 模式
- 批量插入,F(xiàn)indInBatches,F(xiàn)ind/Create with Map,使用 SQL 表達式、Context Valuer 進行 CRUD
- SQL 構建器,Upsert,數(shù)據(jù)庫鎖,Optimizer/Index/Comment Hint,命名參數(shù),子查詢
- 復合主鍵,索引,約束
- Auto Migration
- 自定義 Logger
- 靈活的可擴展插件 API:Database Resolver(多數(shù)據(jù)庫,讀寫分離)、Prometheus…
- 每個特性都經(jīng)過了測試的重重考驗
- 開發(fā)者友好
go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite
其他的補充內容:
- Gorm是軟刪除,為了保證數(shù)據(jù)庫的完整性
三、Navicat
新建連接 -> MySQL -> 連接名隨便 -> 密碼隨便 -> 雙擊左側打開 -> 右鍵information_schema -> 新建數(shù)據(jù)庫 -> 名稱crud-list -> 字符集utf8mb4
這里如果打開的時候報錯navicat 1045 - access denied for user 'root'@'localhost' (using password: 'YES'),則需要查看自己的數(shù)據(jù)庫本身的問題
四、Gin+Gorm的CRUD
連接數(shù)據(jù)庫
編寫測試代碼,成功運行即可,但是這個時候還不能查看數(shù)據(jù)庫是否被創(chuàng)建,
如果要看我們需要定義結構體,然后定義表遷移,具體代碼如下:
package main
import (
"fmt"
"time"
// "gorm.io/driver/sqlite"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 如何連接數(shù)據(jù)庫 ? MySQL + Navicat
// 需要更改的內容:用戶名,密碼,數(shù)據(jù)庫名稱
dsn := "root:BqV?eGcc_1o+@tcp(127.0.0.1:3306)/crud-list?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println("db = ", db)
fmt.Println("err = ", err)
// 連接池
sqlDB, err := db.DB()
// SetMaxIdleConns 設置空閑連接池中連接的最大數(shù)量
sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 設置打開數(shù)據(jù)庫連接的最大數(shù)量。
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 設置了連接可復用的最大時間。
sqlDB.SetConnMaxLifetime(10 * time.Second) // 10秒鐘
// 結構體
type List struct {
Name string
State string
Phone string
Email string
Address string
}
// 遷移
db.AutoMigrate(&List{})
// 接口
r := gin.Default()
// 端口號
PORT := "3001"
r.Run(":" + PORT)
}
定義好之后我們運行,沒有報錯并且在終端顯示出來3001就是正確的,這個時候我們可以去Navicat里面查看crud-list下面的"表",刷新后發(fā)現(xiàn)有一個lists產生,那就是對的了。
但是這個時候我們存在兩個問題:
- 沒有主鍵:在struct里添加
gorm.Model來解決,Ctrl+左鍵可以查看model - 表里面的名稱變成了復數(shù):詳見文檔的高級主題-GORM配置里,在
*db*, *err* *:=* gorm.Open(mysql.Open(dsn), *&*gorm.Config{})里面添加一段話即可
更改完成之后我們要先在Navicat里面把原來的表lists刪掉才能重新創(chuàng)建,這個時候我們重新運行,就會發(fā)現(xiàn)表單里面多了很多東西
結構體定義與優(yōu)化
例如:
`gorm:"type:varchar(20); not null" json:"name" binding:"required"`
需要注意的是:
- 結構體里面的變量(Name)必須首字母大寫,否則創(chuàng)建不出列,會被自動忽略
- gorm指定類型
- json表示json接收的時候的名稱
- binding required表示必須傳入
CRUD接口
// 測試
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "請求成功",
})
})
編寫完這一段之后運行代碼,然后去postman里面新建一個GET接口127.0.0.1:3001然后send一下,出現(xiàn)請求成功就請求成功了。
增也是一樣,寫好之后直接用如下JSON來測試就可以:
{
"name" : "張三",
"state" : "在職",
"phone" : "13900000000",
"email" : "6666@qq.com",
"address" : "二仙橋成華大道"
}
返回:
{
"code": "200",
"data": {
"ID": 1,
"CreatedAt": "2023-01-24T09:27:36.73+08:00",
"UpdatedAt": "2023-01-24T09:27:36.73+08:00",
"DeletedAt": null,
"name": "張三",
"state": "在職",
"phone": "13900000000",
"email": "6666@qq.com",
"address": "二仙橋成華大道"
},
"msg": "添加成功"
}
這時候也可以在數(shù)據(jù)庫里看到這條數(shù)據(jù)
刪除也是一樣,編寫完運行之后添加一個DELETE接口,然后輸入127.0.0.1:3001/user/delete/2之后send就可以看到返回了(前提是有數(shù)據(jù))
{
"code": 200,
"msg": "刪除成功"
}
如果是刪除了不存在的id,就會返回
{
"code": 400,
"msg": "id沒有找到,刪除失敗"
}
順帶一提,事實上這個代碼還可以優(yōu)化,這里的if else太多了,后面優(yōu)化的時候有錯誤直接return
修改也是一樣,示例:
{
"name" : "張三",
"state" : "離職",
"phone" : "13900000000",
"email" : "6666@qq.com",
"address" : "二仙橋成華大道"
}
返回:
{
"code": 200,
"msg": "修改成功"
}
查詢分為兩種:
- 條件查詢
- 分頁查詢
條件查詢的話,直接寫好了請求127.0.0.1:3001/user/list/王五
返回:
{
"code": "200",
"data": [
{
"ID": 3,
"CreatedAt": "2023-01-24T10:06:25.305+08:00",
"UpdatedAt": "2023-01-24T10:06:25.305+08:00",
"DeletedAt": null,
"name": "王五",
"state": "在職",
"phone": "13100000000",
"email": "8888@qq.com",
"address": "八仙橋成華大道"
}
],
"msg": "查詢成功"
}
全部 / 分頁查詢的話
譬如說請求是:127.0.0.1:3001/user/list?pageNum=1&pageSize=2
意思就是查詢第一頁的兩個
返回:
{
"code": 200,
"data": {
"list": [
{
"ID": 3,
"CreatedAt": "2023-01-24T10:06:25.305+08:00",
"UpdatedAt": "2023-01-24T10:06:25.305+08:00",
"DeletedAt": null,
"name": "王五",
"state": "在職",
"phone": "13100000000",
"email": "8888@qq.com",
"address": "八仙橋成華大道"
}
],
"pageNum": 1,
"pageSize": 2,
"total": 2
},
"msg": "查詢成功"
}
如果請求是:127.0.0.1:3001/user/list
返回:
{
"code": 200,
"data": {
"list": [
{
"ID": 1,
"CreatedAt": "2023-01-24T09:27:36.73+08:00",
"UpdatedAt": "2023-01-24T09:55:20.351+08:00",
"DeletedAt": null,
"name": "張三",
"state": "離職",
"phone": "13900000000",
"email": "6666@qq.com",
"address": "二仙橋成華大道"
},
{
"ID": 3,
"CreatedAt": "2023-01-24T10:06:25.305+08:00",
"UpdatedAt": "2023-01-24T10:06:25.305+08:00",
"DeletedAt": null,
"name": "王五",
"state": "在職",
"phone": "13100000000",
"email": "8888@qq.com",
"address": "八仙橋成華大道"
}
],
"pageNum": 0,
"pageSize": 0,
"total": 2
},
"msg": "查詢成功"
}
完整代碼如下:
package main
import (
"fmt"
"strconv"
"time"
// "gorm.io/driver/sqlite"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func main() {
// 如何連接數(shù)據(jù)庫 ? MySQL + Navicat
// 需要更改的內容:用戶名,密碼,數(shù)據(jù)庫名稱
dsn := "root:password@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
fmt.Println("db = ", db)
fmt.Println("err = ", err)
// 連接池
sqlDB, err := db.DB()
// SetMaxIdleConns 設置空閑連接池中連接的最大數(shù)量
sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 設置打開數(shù)據(jù)庫連接的最大數(shù)量。
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 設置了連接可復用的最大時間。
sqlDB.SetConnMaxLifetime(10 * time.Second) // 10秒鐘
// 結構體
type List struct {
gorm.Model // 主鍵
Name string `gorm:"type:varchar(20); not null" json:"name" binding:"required"`
State string `gorm:"type:varchar(20); not null" json:"state" binding:"required"`
Phone string `gorm:"type:varchar(20); not null" json:"phone" binding:"required"`
Email string `gorm:"type:varchar(40); not null" json:"email" binding:"required"`
Address string `gorm:"type:varchar(200); not null" json:"address" binding:"required"`
}
// 遷移
db.AutoMigrate(&List{})
// 接口
r := gin.Default()
// 測試
// r.GET("/", func(c *gin.Context) {
// c.JSON(200, gin.H{
// "message": "請求成功",
// })
// })
// 業(yè)務碼約定:正確200,錯誤400
// 增
r.POST("/user/add", func(ctx *gin.Context) {
// 定義一個變量指向結構體
var data List
// 綁定方法
err := ctx.ShouldBindJSON(&data)
// 判斷綁定是否有錯誤
if err != nil {
ctx.JSON(200, gin.H{
"msg": "添加失敗",
"data": gin.H{},
"code": "400",
})
} else {
// 數(shù)據(jù)庫的操作
db.Create(&data) // 創(chuàng)建一條數(shù)據(jù)
ctx.JSON(200, gin.H{
"msg": "添加成功",
"data": data,
"code": "200",
})
}
})
// 刪
// 1. 找到對應的id對應的條目
// 2. 判斷id是否存在
// 3. 從數(shù)據(jù)庫中刪除 or 返回id沒有找到
// Restful編碼規(guī)范
r.DELETE("/user/delete/:id", func(ctx *gin.Context) {
var data []List
// 接收id
id := ctx.Param("id") // 如果有鍵值對形式的話用Query()
// 判斷id是否存在
db.Where("id = ? ", id).Find(&data)
if len(data) == 0 {
ctx.JSON(200, gin.H{
"msg": "id沒有找到,刪除失敗",
"code": 400,
})
} else {
// 操作數(shù)據(jù)庫刪除(刪除id所對應的那一條)
// db.Where("id = ? ", id).Delete(&data) <- 其實不需要這樣寫,因為查到的data里面就是要刪除的數(shù)據(jù)
db.Delete(&data)
ctx.JSON(200, gin.H{
"msg": "刪除成功",
"code": 200,
})
}
})
// 改
r.PUT("/user/update/:id", func(ctx *gin.Context) {
// 1. 找到對應的id所對應的條目
// 2. 判斷id是否存在
// 3. 修改對應條目 or 返回id沒有找到
var data List
id := ctx.Param("id")
// db.Where("id = ?", id).Find(&data) 可以這樣寫,也可以寫成下面那樣
// 還可以再Where后面加上Count函數(shù),可以查出來這個條件對應的條數(shù)
db.Select("id").Where("id = ? ", id).Find(&data)
if data.ID == 0 {
ctx.JSON(200, gin.H{
"msg": "用戶id沒有找到",
"code": 400,
})
} else {
// 綁定一下
err := ctx.ShouldBindJSON(&data)
if err != nil {
ctx.JSON(200, gin.H{
"msg": "修改失敗",
"code": 400,
})
} else {
// db修改數(shù)據(jù)庫內容
db.Where("id = ?", id).Updates(&data)
ctx.JSON(200, gin.H{
"msg": "修改成功",
"code": 200,
})
}
}
})
// 查
// 第一種:條件查詢,
r.GET("/user/list/:name", func(ctx *gin.Context) {
// 獲取路徑參數(shù)
name := ctx.Param("name")
var dataList []List
// 查詢數(shù)據(jù)庫
db.Where("name = ? ", name).Find(&dataList)
// 判斷是否查詢到數(shù)據(jù)
if len(dataList) == 0 {
ctx.JSON(200, gin.H{
"msg": "沒有查詢到數(shù)據(jù)",
"code": "400",
"data": gin.H{},
})
} else {
ctx.JSON(200, gin.H{
"msg": "查詢成功",
"code": "200",
"data": dataList,
})
}
})
// 第二種:全部查詢 / 分頁查詢
r.GET("/user/list", func(ctx *gin.Context) {
var dataList []List
// 查詢全部數(shù)據(jù) or 查詢分頁數(shù)據(jù)
pageSize, _ := strconv.Atoi(ctx.Query("pageSize"))
pageNum, _ := strconv.Atoi(ctx.Query("pageNum"))
// 判斷是否需要分頁
if pageSize == 0 {
pageSize = -1
}
if pageNum == 0 {
pageNum = -1
}
offsetVal := (pageNum - 1) * pageSize // 固定寫法 記住就行
if pageNum == -1 && pageSize == -1 {
offsetVal = -1
}
// 返回一個總數(shù)
var total int64
// 查詢數(shù)據(jù)庫
db.Model(dataList).Count(&total).Limit(pageSize).Offset(offsetVal).Find(&dataList)
if len(dataList) == 0 {
ctx.JSON(200, gin.H{
"msg": "沒有查詢到數(shù)據(jù)",
"code": 400,
"data": gin.H{},
})
} else {
ctx.JSON(200, gin.H{
"msg": "查詢成功",
"code": 200,
"data": gin.H{
"list": dataList,
"total": total,
"pageNum": pageNum,
"pageSize": pageSize,
},
})
}
})
// 端口號
PORT := "3001"
r.Run(":" + PORT)
}到此這篇關于Gin+Gorm實戰(zhàn)CRUD的文章就介紹到這了,更多相關Gin Gorm CRUD內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用golang腳本基于kubeadm創(chuàng)建新的token(問題分析)
這篇文章主要介紹了使用golang腳本基于kubeadm創(chuàng)建新的token(問題分析),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-10-10

