Gin 学习+Gorm
名字 | 链接 | 备注 |
---|---|---|
gin框架 | https://gin-gonic.com/zh-cn/docs/ | |
gorm 持久层框架 | https://github.com/go-gorm/gorm | |
视频教学链接 | ||
1. 简介
1.1 简介
框架对比
gin
一个轻量级的高性能的Web框架,具有快速、高效、易用等特点,可以用于快速构建RESTful API和Web应用程序。它采用了类似于Martini的API设计,但性能更好。
- 性能高:Gin采用了基于radix树的路由实现,可以快速匹配URL和处理请求,性能非常高。
- 功能丰富:Gin提供了丰富的中间件支持,可以方便地进行路由、参数解析、日志记录等操作。
- 易于学习:Gin的API设计简洁明了,易于学习和使用。
Beego
Beego是一个开源的全栈式的Web框架,它采用了MVC架构,支持自动化路由、ORM、Session、日志、缓存等功能,并提供了丰富的工具和库,适合快速构建Web应用。
Beego它的灵感来自 Tornado、Sinatra 和 Flask。Beego 是一个 RESTful HTTP 框架,用于快速开发 Go 语言企业应用程序,例如 RESTful API、Web 应用程序和后端服务。它集成了 Go 语言特定的功能,例如接口和结构嵌入。
Beego具有以下优点:
功能全面:Beego提供了ORM、Session、Cache等多种功能的支持,可以方便地进行Web开发。
文档丰富:Beego的文档非常丰富,对于各种功能的使用都有详细的说明。
易于扩展:Beego支持插件机制,可以方便地进行扩展和定制。
缺点:
- Beego的性能相对较低,不适合高并发场景。
- Beego的代码结构较为复杂,学习成本较高。
echo
Echo是一个快速、简单、轻量级的Web框架,它采用了类似于Gin的API设计,但更加简洁。
Echo的优点是快速、易用,缺点是功能相对较少。
Echo具有以下优点:
简单易用:Echo的API设计简单易用,学习成本较低。
性能高:Echo采用了高性能的HTTP路由实现,可以快速地处理请求。
中间件支持:Echo提供了丰富的中间件支持,可以方便地进行路由、参数解析、JWT验证等操作。
缺点:
Echo的文档相对较少,对于一些高级功能的实现需要自己探索。
Echo的功能相对较简单,不适合复杂的Web应用场景。
Iris
Iris是一个快速、简单、易用的Web框架,它采用了MVC架构,支持自动化路由、ORM、Session、模板引擎等功能,并提供了丰富的工具和库。
Iris具有以下有点:
速度快:Iris的性能非常出色,可以处理大量的并发请求。
简单易用:Iris的API设计简单易懂,学习成本较低。
灵活可扩展:Iris提供了丰富的中间件、插件等扩展机制,可以根据需求进行灵活扩展。
Iris的缺点:
相对较新,生态相对较少。
文档相对来说比较简单、不够完善,对于初学者来说可能需要耗费一些时间来学习。
chi
chi具有以下优点:
轻量级:chi的代码量非常少,可以快速上手。
高性能:chi的性能非常出色,可以处理大量的并发请求。
灵活可扩展:chi提供了许多中间件和扩展机制,可以根据需求进行灵活扩展。
chi的缺点:是文档相对来说比较简单,对于初学者来说可能需要耗费一些时间来学习。
Revel
Revel是一个全栈Web框架,它采用了MVC架构,支持自动化路由、ORM、Session、模板引擎等功能,并提供了丰富的工具和库,可以快速开发高性能、可扩展的Web应用程序。
Revel的优点是功能齐全、易用,缺点是。
Revel具有以下优点:
全栈框架:Revel提供了完整的Web开发框架,包括路由、模板、ORM等,可以快速搭建Web应用程序。
高性能:Revel的性能非常出色,可以处理大量的并发请求。
可扩展:Revel提供了许多插件和扩展机制,可以根据需求进行灵活扩展。
Revel的缺点:
性能相对较低
学习曲线相对来说比较陡峭,需要一定的学习成本。
链接 | star数 | |
---|---|---|
Gin | https://github.com/gin-gonic/gin | 78k |
Beego | https://github.com/beego/beego | 31.4k |
Echo | https://github.com/labstack/echo | 29.5k |
Iris | https://github.com/kataras/iris | 25.2k |
CHI | https://github.com/go-chi/chi | 13.1k |
1.2 go mod学习
初始化模块
go mod init <项目模块名称>
go mod edit:编辑go.mod文件,选项有-json、-require和-exclude,可以使用帮助go help mod edit
go mod graph:以文本模式打印模块需求图
案例
依赖关系处理 ,根据go.mod文件
go mod tidy
将依赖包复制到项目下的 vendor目录。
go mod vendor
# 如果包被屏蔽(墙),可以使用这个命令,随后使用go build -mod=vendor编译
验证依赖是否正确
go mod verify
更换下载源
go env -w GOPROXY=https://goproxy.cn,direct
显示依赖关系
go list -m all
显示详细依赖关系
go list -m -json all
下载依赖
go mod download [path@version]
1.3 安装
首先新建一个空项目
然后再命令行输入
go mod init demo
然后再输入
go mod tidy
安装gin
go get -u github.com/gin-gonic/gin
-u 已存在相关的代码包,强行更新代码包及其依赖包
- 七牛云
- 阿里云
- 清华大学云
2种方式更新配置
1. 通过环境变量配置 在终端中执行以下命令
go env -w GOPROXY=https://goproxy.cn,direct
2. 通过配置文件配置 你也可以在Go的配置文件中手动修改GOPROXY的值。找到并编辑go
的配置文件(通常位于$HOME/.config/go/env
),添加或修改以下行:
GOPROXY=https://goproxy.cn,direct
编写文件
package main
import "github.com/gin-gonic/gin"
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
如果配置完代理,代码爆红
这里就会出现包的引用
运行代码
go run main.go
访问路径
http://localhost:8080
1.4 常见的框架结结构
一般设计一个常规的web项目,都需要以下几个模块
runApp 主函数,运行整个项目 routes 路由控制,管理跳转以及路由分组 controllers 管理路由跳转后执行的逻辑 service/serviceImp 管理执行的具体业务,依赖注入时可以做实体接口分离 dao 管理数据库连接,数据库控制 filters 中间件,全局,路由分组中注入额外的执行方法,例如token,cors logs 日志系统,交给运维处理 configs 额外的属性配置,例如数据库连接信息,文件上传大小,线程池大小
2. 入门
下载测试api的工具-apifox
2.1 restful风格的代码
package main
import "github.com/gin-gonic/gin"
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/user/info", func(c *gin.Context) {
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
})
})
r.POST("/user/info", func(c *gin.Context) {
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
})
})
r.PUT("/user/info", func(c *gin.Context) {
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
})
})
r.DELETE("/user/info", func(c *gin.Context) {
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
2.2 分组
分组
把同一个前缀的路由放在一起
package main
import "github.com/gin-gonic/gin"
func login(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "success",
})
}
func submit(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "success",
})
}
func main() {
//Default返回一个默认的路由引擎
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/login", login)
v1.POST("/submit", submit)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", login)
v2.POST("/submit", submit)
}
router.Run(":8080")
}
启动的时候,会自动凭借上路由
使用apifox进行访问
当项目变大的时候,一个main.go文件放上一大堆文件,这不合理
统一json返回
新建文件夹
package controllers
import "github.com/gin-gonic/gin"
type JsonResult struct {
Code int `json:"code"`
Msg interface{} `json:"msg"`
Data interface{} `json:"data"`
Count int `json:"count"`
}
func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) {
c.JSON(200, JsonResult{
Code: code,
Msg: msg,
Data: data,
Count: int(count),
})
}
func ReturnError(c *gin.Context, code int, msg interface{}) {
c.JSON(200, JsonResult{
Code: code,
Msg: msg,
})
}
controller
创建文件
下面是代码
package controllers
import "github.com/gin-gonic/gin"
func GetUserInfo(c *gin.Context) {
ReturnSuccess(c, 200, "success", "测试数据,使用的user", 0)
}
修改路由文件
代码内容
package router
import (
"demo/controllers"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
//Default返回一个默认的路由引擎
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/userInfo", controllers.GetUserInfo)
}
return router
}
测试的结果
也可以换一种方式进行绑定
package controllers
import "github.com/gin-gonic/gin"
type OrderController struct {
}
// GetOrderList 定义了一个接收者为 OrderController 的方法 GetOrderList,该方法接受一个参数 c,类型为 *gin.Context。
func (o OrderController) GetOrderList(c *gin.Context) {
ReturnSuccess(c, 200, "success", []int{1, 2, 3}, 0)
}
这样子就不会发生方法名字冲突的问题
package router
import (
"demo/controllers"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
//Default返回一个默认的路由引擎
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/userInfo", controllers.GetUserInfo)
v1.POST("/orderList", controllers.OrderController{}.GetOrderList)
}
return router
}
2.3 参数传递
方法 | 名称 | 备注 |
---|---|---|
value := c.Query("key") | 通过 c.Query 方法获取 URL 中的 Query 参数。 | /user/search?username=少林&address=北京 |
values := c.QueryArray("key") | 通过 c.QueryArray v获取 Query 参数的数组。 | |
value := c.Param("paramName") | 通过 c.Param 获取 URL 中的路由参数。 | |
value := c.DefaultQuery("key", "defaultValue") | 通过 c.DefaultQuery 方法获取 Query 参数,如果参数不存在,则返回默认值。 | |
value := c.PostForm("key") | 通过 c.PostForm 方法获取 POST 请求的表单参数。 | |
values := c.PostFormArray("key") | 通过 c.PostFormArray 获取 POST 请求的表单参数的数组。 | |
c.ShouldBind(&input) | 通过 c.ShouldBind 或 c.ShouldBindJSON 、 c.ShouldBindXML等方法,将请求的数据绑定到结构体中。 | |
Get-QueryString传递
/user/search?username=少林&address=北京
package main
import "github.com/gin-gonic/gin"
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/user/search", func(c *gin.Context) {
//username := c.DefaultQuery("username", "少林")
username:= c.Query("username")
address := c.Query("address")
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
"username": username,
"address": address,
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
Get-通过路径传递(Pathinfo)
/user/search/少林/北京
package main
import "github.com/gin-gonic/gin"
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/user/info/:username/:address", func(c *gin.Context) {
//username := c.DefaultQuery("username", "少林")
username := c.Param("username")
address := c.Param("address")
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
"username": username,
"address": address,
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
POST-表单提交
POST /user/search/
package main
import "github.com/gin-gonic/gin"
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.POST("/user/info", func(c *gin.Context) {
username := c.PostForm("username")
address := c.PostForm("address")
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
"username": username,
"address": address,
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
测试内容
POST-Json
为什么要参数绑定,本质上是方便,提高开发效率
A. 通过反射的机制,自动提取querystring、form表单、json、xml等参数到struct中
B. 通过http协议中的context type,识别是json、xml或者表单
json格式的数据,只能使用结构体或者map来接受
package main
import "github.com/gin-gonic/gin"
type UserInfo struct {
Username string `form:"username" json:"username"`
Address string `form:"password" json:"address"`
}
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.POST("/user/info", func(c *gin.Context) {
var u UserInfo
err := c.ShouldBind(&u)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"errpr": err.Error(),
})
} else {
fmt.Printf("%#v\n", u)
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"data": u,
})
}
})
r.Run() // listen and serve on 0.0.0.0:8080
}
返回结果
文件上传
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// Set a lower memory limit for multipart forms (default is 32 MiB)
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// single file
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
// Upload the file to specific dst.
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
})
})
router.Run(":8080")
}
多文件上传
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// Set a lower memory limit for multipart forms (default is 32 MiB)
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%d files uploaded!", len(files)),
})
})
router.Run(":8080")
}
2.4 实现异常捕获
2.5 使用gorm
名称 | 链接 | 备注 |
---|---|---|
中文文档 | https://topgoer.com | |
官方文档 | https://gorm.io/zh_CN/docs/ | |
Gorm 是 Go 的一个 ORM(对象关系映射)库。它提供了一个简单易用的 API,用于与数据库交互、处理数据库迁移 和执行常见的数据库操作,如查询、插入、更新和删除记录。它支持多种数据库后端,包括 MySQL、PostgreSQL SQLite 等。
安装框架和连接驱动
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
创建表
create table user
(
id bigint auto_increment comment '主键ID'
primary key,
name varchar(30) null comment '姓名',
age int null comment '年龄',
email varchar(50) null comment '邮箱'
);
快速demo
package main
import (
"fmt"
"gorm.io/driver/mysql" //引入mysql驱动
"gorm.io/gorm"
)
func main() {
//连接数据库:
//参数:指的是数据库的设置信息:用户名:密码@tcp(ip:port)/数据库名字?charset=utf8&parseTime=True&loc=Local
//charset=utf8设置字符集
//parseTime=True为了处理time.Time
//loc=Local时区设置,与本地时区保持一致
dsn := "root:root@tcp(127.0.0.1:3306)/testgorm?charset=utf8&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err) //如果出错,后续代码没有必要执行,想让程序中断,panic来执行即可
}
//创建表方式一:通常情况下,数据库中新建的表的名字是结构体名字的复数形式,例如结构体User,表名users
if err := db.AutoMigrate(&User{}); err != nil {
panic(err)
}
//创建表方式二:Table方法可以指定你要创建的数据库的表名
if err := db.Table("user").AutoMigrate(&User{}); err != nil {
panic(err)
}
//删除表
if !db.Migrator().HasTable(&User{}) {
fmt.Println("table does not exist")
} else {
if err := db.Migrator().DropTable(&User{}); err != nil {
panic(err)
}
}
}
// 定义结构体
type User struct {
Age int
Name string
}
下面的方法-是在模仿正规的项目,下面是目录结构
创建配置文件
创建config文件夹,以及dao文件。分别放置配置文件以及,连接的文件
package config
const (
//连接数据库:
//参数:指的是数据库的设置信息:用户名:密码@tcp(ip:port)/数据库名字?charset=utf8&parseTime=True&loc=Local
//charset=utf8设置字符集
//parseTime=True为了处理time.Time
//loc=Local时区设置,与本地时区保持一致
MysqdblUrl = "root:root@tcp(127.0.0.1:3306)/mockito?charset=utf8mb4&parseTime=True&loc=Local"
)
数据库我本地只有mockito就使用这个了
创建DAO
package dao
import (
"demo/config" // 获取配置文件,demo是因为我项目的名字就是demo
"fmt"
"gorm.io/driver/mysql" //引入mysql驱动
"gorm.io/gorm" // 使用gorm框架
"time"
)
var (
Db *gorm.DB
err error
)
// 定义结构体
type User struct {
Age int
Name string
}
func init() {
Db, err = gorm.Open(mysql.Open(config.MysqdblUrl), &gorm.Config{})
if err != nil {
panic(err) //如果出错,后续代码没有必要执行,想让程序中断,panic来执行即可
} else {
fmt.Printf("数据库连接成功")
}
DB, _ := Db.DB()
// 最大连接数
DB.SetMaxOpenConns(2)
// 最大空闲连接数 <= 最大连接数
DB.SetMaxIdleConns(1)
// 连接数的生命周期, 从创建开始记时,到时间点会被销毁
DB.SetConnMaxLifetime(time.Hour * 7)
// 最大空闲时间, 这个链接距离上一次被使用的时间段,超过这个时间则认为该连接已过期
DB.SetConnMaxIdleTime(time.Hour)
}
创建models文件夹
创建实体类,首先查看数据库里面的字段
package models
import (
"demo/dao"
"fmt"
)
// 【1】模型名称和表名的映射规则:
// 1、如果模型名没有驼峰命名,那么表名就是:模型名小写+复数形式:如模型名User---》表名users
// 2、如果模型名有驼峰命名,那么表名就是:大写变小写并在前面加下划线,最后加复数形式:如模型名UserInfo---》表名user_infos
// 3、如果模型名有连续的大写字母,那么表名就是:连续的大写字母变小写,驼峰前加下划线,字母变小写,最后加复数形式:如模型名:DBUserInfo---》表名db_user_infos
type User struct {
Id int `gorm:"column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
Age int `gorm:"column:age" json:"age"`
Email string `gorm:"column:email" json:"email"`
}
// TableName 返回用户表的名称。
// 该方法没有输入参数。
//
// 返回值:
//
// string: 表名
func (User) TableName() string {
return "user"
}
func GetUserTest(id int) (User, error) {
var user User
fmt.Printf("GetUserTest id:%d\n", id)
err := dao.Db.Where("id = ?", id).First(&user).Error
return user, err
}
编写controller层
package controllers
import (
"demo/models"
"github.com/gin-gonic/gin"
"strconv"
)
func GetQueryString(c *gin.Context) {
userIdStr := c.Param("userId")
userId, err := strconv.Atoi(userIdStr)
if(err != nil){
ReturnError(c, 500, "参数错误")
}
user, _ := models.GetUserTest(userId)
//输出json结果给调用方
c.JSON(200, gin.H{
"message": "pong",
"user": user,
})
}
更换router
package router
import (
"demo/controllers"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
//Default返回一个默认的路由引擎
router := gin.Default()
// Simple group: v1
v2 := router.Group("/user")
{
v2.GET("/search", controllers.GetQueryString)
}
return router
}
这里
goland每次都自动删除我import的包
第一步:取消Optimize imports on the fly勾选
第二步:取消Optimize imports
2.6 gorm的crud
整体的项目结构
新增
在models下面新增一条方法
func AddUser(name string, age int, email string) (User, error) {
user := User{Name: name, Age: age, Email: email}
err := dao.Db.Create(&user).Error
return user, err
}
在controller里面新增数据。ReturnError,ReturnSuccess是之前写的一个方法。可以换成其他的任意返回方法
func CreateUser(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
ReturnError(c, 500, "参数错误")
}
user, err := models.AddUser(user.Name, user.Age, user.Email)
if err != nil {
ReturnError(c, 500, "添加用户失败")
} else {
ReturnSuccess(c, 200, "添加用户成功", user, 1)
}
}
然后在routers.go里面添加路由
v2.POST("/add", controllers.CreateUser)
然后使用postman进行新增
注意,表语句是自增的。如果不是自增的,json里面需要新增ID
-- auto-generated definition
create table user
(
id bigint auto_increment comment '主键ID'
primary key,
name varchar(30) null comment '姓名',
age int null comment '年龄',
email varchar(50) null comment '邮箱'
);
修改
更新多个列
func UpdateUser(id int, name string, age int, email string) (User, error) {
user := User{Name: name, Age: age, Email: email}
err := dao.Db.Model(&user).Where("id = ?", id).Updates(&user).Error
return user, err
}
修改controller层
func UpdateUser(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
ReturnError(c, 500, "参数错误")
}
user, err := models.UpdateUser(user.Id, user.Name, user.Age, user.Email)
if err != nil {
ReturnError(c, 500, "更新用户失败")
} else {
ReturnSuccess(c, 200, "更新用户成功", user, 1)
}
}
路由新增
v2.GET("/update", controllers.UpdateUser)
先看一下原来的数据
访问链接
修改后的
保存所有列
Save
会保存所有的字段,即使字段是零值
func UpdateAll(id int, name string, age int, email string) (User, error) {
var user User = User{Name: name, Age: age, Email: email}
err := dao.Db.Save(&user).Error
return user, err
}
更新某几个列
func updateUser(id int, name string, age int, email string) {
user := User{Id:id, Name: name, Age: age, Email: email}
//带条件的更新。所有满足条件的记录都会被更新
dao.Db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
//UPDATE users SET name='hello',updated ='2013-11-17 21:34:10' WHERE active=true;
//User's ID is `111': 根据id进行判断的
dao.Db.Model(&user).Update("name", "hello")
//UPDATE users SET name='hello',updated at='2013-11-17 21:34:10'WHERE id=111;
//Update with conditions and model value
dao.Db.Model(&user).Where("active = ?", true).Update("name", "hello")
//UPDATE users SET name='hello',updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
}
删除
删除一条记录
删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:
func DeleteUser(id int) (int, error) {
err := dao.Db.Where("id = ?", id).Delete(&User{Id: id}).Error
return 1, err
}
根据主键删除
GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);
批量删除
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
可以将一个主键切片传递给Delete
方法,以便更高效的删除数据量大的记录
var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);
db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3);
查询列表
查询 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
单个对象-First
GORM 提供了 First
、Take
、Last
方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1
条件,且没有找到记录时,它会返回 ErrRecordNotFound
错误、
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
多个对象-Find
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
Or条件
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
排序
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
limit 和offset
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)