vault backup: 2026-06-07 12:14:39
This commit is contained in:
+97
-279
@@ -1,362 +1,180 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- gin
|
||||
- web框架
|
||||
- http
|
||||
tags: [go, golang, gin, Web框架, HTTP]
|
||||
create time: 2026-06-07 15:20
|
||||
---
|
||||
|
||||
# Gin
|
||||
|
||||
Gin是一个用Go语言编写的高性能Web框架,以其高效的路由系统和优秀的性能著称。本文将详细介绍如何使用Gin框架开发Web应用。
|
||||
## 概述
|
||||
|
||||
## 安装Gin
|
||||
Gin 是 Go 生态中最流行的 Web 框架,以高性能路由和简洁的 API 著称。本文从基础用法到中间件、分组路由,覆盖构建 RESTful API 的核心技能。
|
||||
|
||||
首先,我们需要安装Gin框架。可以使用以下命令:
|
||||
## 正文
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
go get -u github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
### 快速开始
|
||||
|
||||
### 创建HTTP服务器
|
||||
> [!question] 💭 思考
|
||||
> 如果让你从零实现一个 HTTP 服务器,需要处理哪些事情?(路由匹配、请求解析、响应格式化……)
|
||||
|
||||
使用Gin创建一个基本的HTTP服务器非常简单:
|
||||
Gin 封装了这些底层细节,让你专注于业务逻辑:
|
||||
|
||||
```go
|
||||
package main
|
||||
r := gin.Default() // 创建默认引擎(含 Logger + Recovery 中间件)
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "pong"})
|
||||
})
|
||||
|
||||
func main() {
|
||||
// 创建默认的路由引擎
|
||||
r := gin.Default()
|
||||
|
||||
// 注册一个GET路由
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "pong",
|
||||
})
|
||||
})
|
||||
|
||||
// 启动HTTP服务,默认在0.0.0.0:8080启动服务
|
||||
r.Run()
|
||||
}
|
||||
r.Run(":8080") // 监听 8080 端口
|
||||
```
|
||||
|
||||
### 路由参数
|
||||
> [!tip] 💡 Default vs New
|
||||
> - `gin.New()`:纯引擎,无日志和恢复中间件
|
||||
> - `gin.Default()`:预装 Logger 和 Recovery,生产环境推荐
|
||||
> - Recovery 中间件会自动 recover panic,避免服务崩溃
|
||||
|
||||
Gin支持多种路由参数形式:
|
||||
### 路由与参数
|
||||
|
||||
#### 路径参数
|
||||
|
||||
```go
|
||||
// 获取get请求的路径参数,参数名为name
|
||||
r.GET("/user/:name", func(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
c.String(http.StatusOK, "Hello %s", name)
|
||||
})
|
||||
|
||||
// 获取get请求的路径参数,参数名为name,age,age的默认值为20
|
||||
r.GET("/users", func(c *gin.Context) {
|
||||
name := c.Query("name") // 获取name参数
|
||||
age := c.DefaultQuery("age", "20") // 获取age参数,默认值为20
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"name": name,
|
||||
"age": age,
|
||||
})
|
||||
name := c.Param("name") // 获取 :name 的值
|
||||
c.String(200, "Hello %s", name)
|
||||
})
|
||||
```
|
||||
|
||||
### 处理表单数据
|
||||
|
||||
Gin可以轻松处理POST请求中的表单数据:
|
||||
#### Query 参数
|
||||
|
||||
```go
|
||||
r.POST("/form", func(c *gin.Context) {
|
||||
// 获取表单参数
|
||||
username := c.PostForm("username")
|
||||
password := c.DefaultPostForm("password", "")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
r.GET("/search", func(c *gin.Context) {
|
||||
q := c.Query("q", "") // 获取 ?q=xxx,默认值 ""
|
||||
page := c.DefaultQuery("page", "1") // 带默认值
|
||||
c.JSON(200, gin.H{"query": q, "page": page})
|
||||
})
|
||||
```
|
||||
|
||||
### 处理JSON数据
|
||||
|
||||
Gin可以轻松处理POST请求中的JSON数据:
|
||||
#### POST 表单 / JSON
|
||||
|
||||
```go
|
||||
// 定义请求体结构
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
r.POST("/login", func(c *gin.Context) {
|
||||
var login LoginRequest
|
||||
|
||||
// 将请求体绑定到结构体
|
||||
if err := c.BindJSON(&login); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理登录逻辑
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "登录成功",
|
||||
"username": login.Username,
|
||||
})
|
||||
})
|
||||
|
||||
// 也可以直接使用map接收JSON数据
|
||||
r.POST("/raw", func(c *gin.Context) {
|
||||
var data map[string]interface{}
|
||||
|
||||
if err := c.BindJSON(&data); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, data)
|
||||
// 处理登录...
|
||||
c.JSON(200, gin.H{"message": "ok"})
|
||||
})
|
||||
```
|
||||
|
||||
在上面的示例中:
|
||||
1. 使用结构体接收JSON数据时,可以通过tag来指定字段的验证规则
|
||||
2. `binding:"required"`表示该字段是必需的
|
||||
3. 也可以使用map来接收不固定结构的JSON数据
|
||||
4. `c.BindJSON()`会自动检查Content-Type是否为application/json
|
||||
|
||||
### 上传文件
|
||||
|
||||
Gin支持单文件和多文件上传:
|
||||
|
||||
```go
|
||||
// 单文件上传
|
||||
r.POST("/upload", func(c *gin.Context) {
|
||||
file, _ := c.FormFile("file")
|
||||
|
||||
// 保存文件
|
||||
c.SaveUploadedFile(file, "./"+file.Filename)
|
||||
|
||||
c.String(http.StatusOK, "File %s uploaded successfully", file.Filename)
|
||||
})
|
||||
|
||||
// 多文件上传
|
||||
r.POST("/uploads", func(c *gin.Context) {
|
||||
form, _ := c.MultipartForm()
|
||||
files := form.File["files"]
|
||||
|
||||
for _, file := range files {
|
||||
c.SaveUploadedFile(file, "./"+file.Filename)
|
||||
}
|
||||
|
||||
c.String(http.StatusOK, "%d files uploaded successfully", len(files))
|
||||
})
|
||||
```
|
||||
> [!warning] ⚠️ 绑定注意事项
|
||||
> - `ShouldBindJSON` 仅在 Content-Type 为 `application/json` 时生效
|
||||
> - `binding:"required"` 由内置验证器检查字段是否为空
|
||||
> - 对于复杂验证需求,建议使用 `github.com/go-playground/validator`
|
||||
|
||||
### 中间件
|
||||
|
||||
Gin的中间件可以在请求处理过程中执行一些公共的操作:
|
||||
> [!question] 💭 思考
|
||||
> 如果你的 API 需要对所有请求做日志记录、鉴权、CORS 处理,在每个 handler 里重复写代码合理吗?
|
||||
|
||||
中间件是横切关注点的标准解决方案——它在请求到达 handler **之前**和返回响应**之后**执行:
|
||||
|
||||
```go
|
||||
// 定义一个中间件
|
||||
func Logger() gin.HandlerFunc {
|
||||
// 自定义中间件
|
||||
func RequestID() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
t := time.Now()
|
||||
|
||||
// 在请求之前
|
||||
|
||||
c.Next()
|
||||
|
||||
// 在请求之后
|
||||
latency := time.Since(t)
|
||||
log.Printf("请求处理时间: %v", latency)
|
||||
id := uuid.New().String()
|
||||
c.Set("requestID", id) // 存入上下文
|
||||
c.Header("X-Request-ID", id)
|
||||
c.Next() // ✅ 必须调用,继续处理后续中间件/handler
|
||||
// 这里可以记录耗时等后处理逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 使用中间件
|
||||
r.Use(Logger())
|
||||
// 注册方式
|
||||
r.Use(RequestID()) // 全局中间件
|
||||
r.GET("/api", MyHandler) // 受中间件保护
|
||||
```
|
||||
|
||||
### 分组路由
|
||||
> [!info] ℹ️ 中间件执行顺序
|
||||
> 先注册的先执行(类似洋葱模型):
|
||||
> ```
|
||||
> 请求 → Logger → Auth → CORS → Handler
|
||||
> 响应 ← Logger ← Auth ← CORS ← Handler
|
||||
> ```
|
||||
|
||||
Gin支持路由分组,可以更好地组织API:
|
||||
#### 分组路由
|
||||
|
||||
```go
|
||||
// 创建v1版本的路由组
|
||||
v1 := r.Group("/v1")
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
v1.POST("/login", loginEndpoint)
|
||||
v1.POST("/submit", submitEndpoint)
|
||||
v1.POST("/read", readEndpoint)
|
||||
v1.GET("/users", getUsers)
|
||||
v1.POST("/users", createUser)
|
||||
v1.PUT("/users/:id", updateUser)
|
||||
v1.DELETE("/users/:id", deleteUser)
|
||||
}
|
||||
|
||||
// 创建v2版本的路由组
|
||||
v2 := r.Group("/v2")
|
||||
{
|
||||
v2.POST("/login", loginEndpointV2)
|
||||
v2.POST("/submit", submitEndpointV2)
|
||||
v2.POST("/read", readEndpointV2)
|
||||
}
|
||||
// 为组注册中间件
|
||||
authGroup := r.Group("/api", AuthMiddleware())
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
以下是一个完整的示例,展示了如何使用Gin框架创建一个简单的RESTful API服务:
|
||||
### RESTful 完整示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// User 用户模型
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
// 模拟数据库
|
||||
var users = []User{
|
||||
{ID: 1, Name: "张三", Age: 25},
|
||||
{ID: 2, Name: "李四", Age: 30},
|
||||
}
|
||||
var users []User
|
||||
|
||||
func main() {
|
||||
// 创建默认的路由引擎
|
||||
r := gin.Default()
|
||||
|
||||
// 注册路由
|
||||
r.GET("/users", getUsers)
|
||||
r.GET("/users/:id", getUserByID) // 这里的冒号表示路径参数,可以通过c.Param("id")获取
|
||||
r.POST("/users", createUser)
|
||||
r.PUT("/users/:id", updateUser)
|
||||
r.DELETE("/users/:id", deleteUser)
|
||||
|
||||
// 启动http服务器,监听8080端口
|
||||
if err := r.Run(":8080"); err != nil {
|
||||
log.Fatalf("Server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有用户
|
||||
func getUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
// 根据ID获取用户
|
||||
func getUserByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
for _, user := range users {
|
||||
if string(user.ID) == id {
|
||||
c.JSON(http.StatusOK, user)
|
||||
return
|
||||
r.GET("/users", func(c *gin.Context) {
|
||||
c.JSON(200, users)
|
||||
})
|
||||
r.GET("/users/:id", func(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
for _, u := range users {
|
||||
if u.ID == id {
|
||||
c.JSON(200, u)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
|
||||
}
|
||||
c.JSON(404, gin.H{"error": "not found"})
|
||||
})
|
||||
r.POST("/users", func(c *gin.Context) {
|
||||
var u User
|
||||
c.ShouldBindJSON(&u)
|
||||
u.ID = len(users) + 1
|
||||
users = append(users, u)
|
||||
c.JSON(201, u)
|
||||
})
|
||||
|
||||
// 创建用户
|
||||
func createUser(c *gin.Context) {
|
||||
var newUser User
|
||||
if err := c.BindJSON(&newUser); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
users = append(users, newUser)
|
||||
c.JSON(http.StatusCreated, newUser)
|
||||
}
|
||||
|
||||
// 更新用户
|
||||
func updateUser(c *gin.Context) {
|
||||
// 获取路径参数中的ID
|
||||
id := c.Param("id")
|
||||
var updatedUser User
|
||||
|
||||
// 绑定请求体中的JSON数据到updatedUser
|
||||
if err := c.BindJSON(&updatedUser); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for i, user := range users {
|
||||
if string(user.ID) == id {
|
||||
users[i] = updatedUser
|
||||
c.JSON(http.StatusOK, updatedUser)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
func deleteUser(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
for i, user := range users {
|
||||
if string(user.ID) == id {
|
||||
users = append(users[:i], users[i+1:]...)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
### 程序测试
|
||||
> [!tip] 💡 性能提示
|
||||
> - `gin.H` 是 `map[string]interface{}` 的别名,在高频场景中考虑使用自定义结构体替代,减少反射开销
|
||||
> - 启用 Gzip 压缩:`r.Use(gzip.Gzip(gzip.DefaultCompression))`
|
||||
> - 生产环境建议关闭 Debug 模式:`gin.SetMode(gin.ReleaseMode)`
|
||||
|
||||
使用curl命令测试API:
|
||||
## 关联笔记
|
||||
|
||||
```bash
|
||||
# 获取所有用户
|
||||
curl http://localhost:8080/users
|
||||
|
||||
# 获取指定用户
|
||||
curl http://localhost:8080/users/1
|
||||
|
||||
# 创建用户
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"id":3,"name":"王五","age":35}' http://localhost:8080/users
|
||||
|
||||
# 更新用户
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"id":1,"name":"张三丰","age":26}' http://localhost:8080/users/1
|
||||
|
||||
# 删除用户
|
||||
curl -X DELETE http://localhost:8080/users/2
|
||||
```
|
||||
|
||||
### 代码说明
|
||||
|
||||
1. **路由设置**:使用`gin.Default()`创建路由引擎,并注册了基本的RESTful API路由。
|
||||
|
||||
2. **数据模型**:定义了`User`结构体作为数据模型,并使用切片模拟数据库存储。
|
||||
|
||||
3. **请求处理**:实现了增删改查(CRUD)的基本操作,包括:
|
||||
- GET /users:获取所有用户
|
||||
- GET /users/:id:获取指定用户
|
||||
- POST /users:创建新用户
|
||||
- PUT /users/:id:更新用户
|
||||
- DELETE /users/:id:删除用户
|
||||
|
||||
4. **错误处理**:对各种可能的错误情况进行了处理,并返回适当的HTTP状态码。
|
||||
|
||||
## 小结
|
||||
|
||||
这篇文章简单介绍了Gin框架的基本使用方法,包括路由配置、参数获取、中间件使用等内容。如需了解Gin框架更详细的用法,可以参考:[Gin框架官方文档](https://gin-gonic.com/zh-cn/docs/)
|
||||
- [[hzh/GolangStar/Go语言框架/gorm]] — Gin + GORM 是全栈开发的经典组合
|
||||
- [[hzh/GolangStar/Go语言进阶/Context]] — HTTP 请求天然适合用 context 控制超时和取消
|
||||
|
||||
+134
-208
@@ -1,274 +1,200 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- gorm
|
||||
- mysql
|
||||
- 数据库
|
||||
tags: [go, golang, gorm, ORM, 数据库]
|
||||
create time: 2026-06-07 15:25
|
||||
---
|
||||
|
||||
# GORM
|
||||
|
||||
GORM是一个功能强大的Go语言ORM库,提供了简洁的API来操作数据库。本文将详细介绍如何使用GORM与MySQL数据库进行交互。
|
||||
## 概述
|
||||
|
||||
## 安装GORM和MySQL驱动
|
||||
GORM 是 Go 生态中最流行的 ORM 库,提供简洁的链式 API 操作数据库。本文从连接配置到 CRUD、关联关系和事务,覆盖日常开发的核心用法。
|
||||
|
||||
首先,我们需要安装GORM和MySQL驱动。可以使用以下命令:
|
||||
## 正文
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
go get -u gorm.io/gorm
|
||||
go get -u gorm.io/driver/mysql
|
||||
go get -gorm.io/gorm
|
||||
go get -gorm.io/driver/mysql # MySQL 驱动(按需选择)
|
||||
```
|
||||
|
||||
## 连接数据库
|
||||
|
||||
在使用GORM连接MySQL数据库时,我们需要提供数据源名称(DSN),其中包含用户名、密码、主机地址、端口号和数据库名称等信息。
|
||||
### 连接数据库
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 定义数据源名称
|
||||
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
|
||||
// 连接数据库
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect database: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 初始化连接
|
||||
|
||||
GORM会自动管理数据库连接池,因此我们不需要手动设置最大连接数和最大空闲连接数。不过,如果有特殊需求,可以通过`sql.DB`对象进行设置。
|
||||
|
||||
```go
|
||||
sqlDB, err := db.DB()
|
||||
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get database object: %v", err)
|
||||
log.Fatal("failed to connect database:", err)
|
||||
}
|
||||
|
||||
// 设置最大打开连接数
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
|
||||
// 设置最大空闲连接数
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
// 可选:调优连接池
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxOpenConns(100) // 最大连接数
|
||||
sqlDB.SetMaxIdleConns(10) // 最大空闲连接
|
||||
```
|
||||
|
||||
## 定义模型
|
||||
> [!tip] 💡 性能建议
|
||||
> - `SetMaxOpenConns` 应根据并发量和请求耗时调整——IO 密集型可适当放大
|
||||
> - `SetMaxIdleConns` 一般设为 MaxOpenConns 的 1/4~1/2
|
||||
> - 连接泄漏(未正确关闭)会导致连接池耗尽,务必注意
|
||||
|
||||
在GORM中,我们通过定义结构体来映射数据库表。每个字段对应表中的一列。
|
||||
### 模型定义
|
||||
|
||||
```go
|
||||
// User 结构体映射到数据库中的users表
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey;column:id"` // ID为主键
|
||||
Name string `gorm:"size:255;column:name"` // Name字段最大长度为255
|
||||
Age int `gorm:"column:age"` // Age字段
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"size:255;not null"`
|
||||
Age int
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// 表名默认为复数形式 "users",可通过 TableName 自定义
|
||||
func (User) TableName() string {
|
||||
return "app_users"
|
||||
}
|
||||
```
|
||||
|
||||
## 自动迁移
|
||||
> [!info] ℹ️ GORM 命名约定
|
||||
> - 表名:结构体名的蛇形复数(User → users)
|
||||
> - 字段名:驼峰转蛇形(UserID → user_id)
|
||||
> - 主键:自动使用 ID / uuid / uid 作为主键并自增
|
||||
> - 外键:关联字段名 + `_id`
|
||||
|
||||
GORM提供了自动迁移功能,可以根据模型结构自动创建或更新数据库表。
|
||||
### CRUD 操作
|
||||
|
||||
#### 创建
|
||||
|
||||
```go
|
||||
// 自动迁移User结构体
|
||||
db.AutoMigrate(&User{})
|
||||
```
|
||||
|
||||
## CRUD操作
|
||||
|
||||
### 创建记录
|
||||
|
||||
使用`Create`方法可以向数据库中插入一条新记录。
|
||||
|
||||
```go
|
||||
// 创建一个新的User记录
|
||||
user := User{Name: "张三", Age: 25}
|
||||
result := db.Create(&user)
|
||||
if result.Error != nil {
|
||||
log.Fatalf("failed to create user: %v", result.Error)
|
||||
}
|
||||
result := db.Create(&user) // 自动回填 user.ID
|
||||
fmt.Println(user.ID) // 查看插入后的 ID
|
||||
```
|
||||
|
||||
### 查询记录
|
||||
|
||||
#### 单行查询
|
||||
|
||||
使用`First`方法可以查询符合条件的第一条记录。
|
||||
#### 查询
|
||||
|
||||
```go
|
||||
// 查询ID为1的User记录
|
||||
var user User
|
||||
result := db.First(&user, 1) // 根据主键查询
|
||||
if result.Error != nil {
|
||||
log.Printf("failed to find user: %v", result.Error)
|
||||
}
|
||||
// 根据主键查询
|
||||
var u1 User
|
||||
db.First(&u1, 1) // SELECT * FROM users WHERE id = 1
|
||||
|
||||
// 条件查询
|
||||
var u2 []User
|
||||
db.Where("age > ?", 18).Find(&u2)
|
||||
|
||||
// 链式组合
|
||||
db.Where("name LIKE ?", "%张%").Order("age desc").Limit(10).Find(&u2)
|
||||
```
|
||||
|
||||
#### 多行查询
|
||||
> [!question] 💭 ORM vs 原生 SQL
|
||||
> GORM 的链式调用确实优雅,但在复杂查询场景下是否值得牺牲 SQL 的表达能力?思考你的项目中哪些查询可以用 GORM 表达,哪些需要手写 SQL。
|
||||
|
||||
使用`Find`方法可以查询符合条件的多条记录。
|
||||
#### 更新
|
||||
|
||||
```go
|
||||
// 查询年龄大于20的所有User记录
|
||||
var users []User
|
||||
result := db.Where("age > ?", 20).Find(&users)
|
||||
if result.Error != nil {
|
||||
log.Printf("failed to find users: %v", result.Error)
|
||||
}
|
||||
// 更新单个字段
|
||||
db.Model(&user).Update("name", "李四")
|
||||
|
||||
// 更新多个字段
|
||||
db.Model(&user).Updates(User{Name: "王五", Age: 30})
|
||||
|
||||
// 全量更新(注意:会忽略零值字段!)
|
||||
db.Save(&user)
|
||||
```
|
||||
|
||||
### 更新记录
|
||||
> [!warning] ⚠️ Save 的零值陷阱
|
||||
> `Save()` 会用结构体的所有字段执行 UPDATE。如果某个字段是零值(如 `Age: 0`),会被更新为 0!建议使用 `Updates` 并只传需要更新的字段。
|
||||
|
||||
使用`Save`方法可以更新记录。
|
||||
#### 删除
|
||||
|
||||
```go
|
||||
// 更新User记录的Age字段
|
||||
user.Age = 26
|
||||
result := db.Save(&user)
|
||||
if result.Error != nil {
|
||||
log.Fatalf("failed to update user: %v", result.Error)
|
||||
}
|
||||
db.Delete(&user, 1) // 软删除(需启用)或硬删除
|
||||
db.Where("age < ?", 18).Delete(&User{}) // 批量删除
|
||||
```
|
||||
|
||||
### 删除记录
|
||||
> [!note] 📝 软删除
|
||||
> GORM 默认启用软删除——调用 Delete 后记录不会被物理删除,而是设置 `deleted_at` 时间戳。如需物理删除:`db.Unscoped().Delete(&user)`
|
||||
|
||||
使用`Delete`方法可以删除记录。
|
||||
### 关联关系
|
||||
|
||||
```go
|
||||
// 删除User记录
|
||||
result := db.Delete(&user)
|
||||
if result.Error != nil {
|
||||
log.Fatalf("failed to delete user: %v", result.Error)
|
||||
type Profile struct {
|
||||
ID uint
|
||||
UserID uint `gorm:"index"`
|
||||
Bio string
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
ID uint
|
||||
AuthorID uint `gorm:"index"`
|
||||
Title string
|
||||
Author User `gorm:"foreignKey:AuthorID"` // 指定外键
|
||||
}
|
||||
|
||||
// 预加载关联数据
|
||||
db.Preload("Profile").First(&user, 1)
|
||||
// SELECT * FROM profiles WHERE user_id = 1
|
||||
```
|
||||
|
||||
## 事务支持
|
||||
> [!tip] 💡 N+1 问题
|
||||
> 在循环中逐个查询关联数据会产生 N+1 问题。始终使用 `Preload` 一次性加载:
|
||||
> ```go
|
||||
> // ❌ N+1
|
||||
> for _, post := range posts {
|
||||
> db.First(&post.Author)
|
||||
> }
|
||||
>
|
||||
> // ✅ 一次加载
|
||||
> db.Preload("Author").Find(&posts)
|
||||
> ```
|
||||
|
||||
GORM支持事务操作,可以通过`Transaction`方法来执行事务。
|
||||
### 事务
|
||||
|
||||
```go
|
||||
// 使用事务创建两个User记录
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&User{Name: "李四", Age: 30}).Error; err != nil {
|
||||
if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
|
||||
return err // 返回非零错误触发回滚
|
||||
}
|
||||
if err := tx.Create(&User{Name: "Bob"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&User{Name: "王五", Age: 35}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return nil // 返回 nil 则提交
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("transaction failed: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
## 预处理和SQL注入
|
||||
> [!warning] ⚠️ 事务中的常见错误
|
||||
> - 不要在事务中使用全局 `db` 变量,应使用传入的 `tx`
|
||||
> - 事务内的所有操作都必须检查 `.Error`
|
||||
> - 事务必须在同一个连接上执行(GORM 默认保证这一点)
|
||||
|
||||
GORM会自动处理SQL注入问题,因此我们不需要手动拼接SQL语句。所有的查询条件都可以通过链式调用来实现。
|
||||
### ORM vs NORM
|
||||
|
||||
```go
|
||||
// 查询Name为"张三"的User记录
|
||||
db.Where("name = ?", "张三").First(&user)
|
||||
```
|
||||
> [!question] 💭 思考
|
||||
> GORM 属于 ORM(对象关系映射),而 Gin 搭配 sqlx 属于 NORM(无映射)。什么时候该用 ORM,什么时候该用 NORM?
|
||||
|
||||
## 完整示例
|
||||
以下是一个完整的示例代码,展示了如何使用GORM连接MySQL数据库并进行CRUD操作。
|
||||
```go
|
||||
package main
|
||||
| 维度 | ORM (GORM) | NORM (sqlx/raw SQL) |
|
||||
|------|-----------|---------------------|
|
||||
| 开发效率 | 高(链式 API) | 低(需写 SQL) |
|
||||
| 类型安全 | 编译期部分保障 | 完全依赖 SQL 编写 |
|
||||
| 复杂查询 | 有限支持 | 完全自由 |
|
||||
| 性能 | 有额外开销 | 最优 |
|
||||
| 学习曲线 | 低 | 需熟悉 SQL |
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
)
|
||||
> [!tip] 💡 混合策略
|
||||
> 大多数项目可以采用混合模式:简单 CRUD 用 GORM,复杂报表/分析查询用原生 SQL:
|
||||
> ```go
|
||||
> // 简单查询用 GORM
|
||||
> db.Find(&users)
|
||||
>
|
||||
> // 复杂查询用原生 SQL
|
||||
> var results []struct {
|
||||
> Department string
|
||||
> AvgAge float64
|
||||
> }
|
||||
> db.Raw(`SELECT department, AVG(age) as avg_age FROM users GROUP BY department`).Scan(&results)
|
||||
> ```
|
||||
|
||||
// User 结构体映射到数据库中的users表
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey;column:id"` // ID为主键
|
||||
Name string `gorm:"size:255;column:name"` // Name字段最大长度为255
|
||||
Age int `gorm:"column:age"` // Age字段
|
||||
}
|
||||
## 关联笔记
|
||||
|
||||
func main() {
|
||||
// 定义数据源名称
|
||||
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
|
||||
// 连接数据库
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect database: %v", err)
|
||||
}
|
||||
|
||||
// 自动迁移User结构体
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
// 创建一个新的User记录
|
||||
user := User{Name: "张三", Age: 25}
|
||||
result := db.Create(&user)
|
||||
if result.Error != nil {
|
||||
log.Fatalf("failed to create user: %v", result.Error)
|
||||
}
|
||||
|
||||
// 查询ID为1的User记录
|
||||
var queriedUser User
|
||||
result = db.First(&queriedUser, user.ID) // 根据主键查询
|
||||
if result.Error != nil {
|
||||
log.Printf("failed to find user: %v", result.Error)
|
||||
} else {
|
||||
fmt.Printf("Queried User: %+v\n", queriedUser)
|
||||
}
|
||||
|
||||
// 更新User记录的Age字段
|
||||
queriedUser.Age = 26
|
||||
result = db.Save(&queriedUser)
|
||||
if result.Error != nil {
|
||||
log.Fatalf("failed to update user: %v", result.Error)
|
||||
}
|
||||
|
||||
// 删除User记录
|
||||
result = db.Delete(&queriedUser)
|
||||
if result.Error != nil {
|
||||
log.Fatalf("failed to delete user: %v", result.Error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 程序输出
|
||||
|
||||
假设数据库连接成功,程序将输出如下信息:
|
||||
|
||||
```
|
||||
Queried User: {ID:1 Name:张三 Age:25}
|
||||
```
|
||||
|
||||
这表示程序成功创建、查询、更新并删除了一条用户记录。
|
||||
|
||||
### 代码说明
|
||||
|
||||
1. **连接数据库**:使用`gorm.Open`方法连接MySQL数据库,数据源名称(DSN)中包含了数据库连接的必要信息。
|
||||
|
||||
2. **自动迁移**:使用`AutoMigrate`方法根据`User`结构体自动创建或更新数据库表。
|
||||
|
||||
3. **创建记录**:使用`Create`方法向数据库中插入一条新记录。
|
||||
|
||||
4. **查询记录**:使用`First`方法查询符合条件的第一条记录。
|
||||
|
||||
5. **更新记录**:使用`Save`方法更新记录。
|
||||
|
||||
6. **删除记录**:使用`Delete`方法删除记录。
|
||||
|
||||
---
|
||||
|
||||
## 小结
|
||||
这篇文章简单介绍了GORM的基本使用方法,包括连接数据库、定义模型、CRUD操作、事务支持等内容。希望对您有所帮助!,如需了解GORM详细的用法,可以参考:[GORM中文官网](https://gorm.io/zh_CN/docs/)
|
||||
- [[hzh/GolangStar/Go语言框架/gin]] — Gin + GORM 是 Go Web 全栈的经典搭档
|
||||
- [[hzh/GolangStar/Go语言进阶/Context]] — HTTP 请求生命周期与数据库事务结合
|
||||
|
||||
Reference in New Issue
Block a user