Skip to content

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数
Ginhttps://github.com/gin-gonic/gin78k
Beegohttps://github.com/beego/beego31.4k
Echohttps://github.com/labstack/echo29.5k
Irishttps://github.com/kataras/iris25.2k
CHIhttps://github.com/go-chi/chi13.1k

1.2 go mod学习

初始化模块

bash
go mod init <项目模块名>
go mod edit:编辑go.mod文件,选项有-json、-require和-exclude,可以使用帮助go help mod edit
go mod graph:以文本模式打印模块需求图

案例

bash

依赖关系处理 ,根据go.mod文件

bash
go mod tidy

将依赖包复制到项目下的 vendor目录。

bash
go mod vendor 
# 如果包被屏蔽(墙),可以使用这个命令,随后使用go build -mod=vendor编译

验证依赖是否正确

bash
go mod verify

更换下载源

bash
go env -w GOPROXY=https://goproxy.cn,direct

显示依赖关系

bash
go list -m all

显示详细依赖关系

bash
go list -m -json all

下载依赖

go
go mod download [path@version]

1.3 安装

首先新建一个空项目

image-20240920211737095

然后再命令行输入

go
go mod init demo

image-20240920211756256

然后再输入

bash
go mod tidy

安装gin

bash
go get -u github.com/gin-gonic/gin

-u 已存在相关的代码包,强行更新代码包及其依赖包

  • 七牛云
  • 阿里云
  • 清华大学云

2种方式更新配置

1. 通过环境变量配置 在终端中执行以下命令

bash
go env -w GOPROXY=https://goproxy.cn,direct

2. 通过配置文件配置 你也可以在Go的配置文件中手动修改GOPROXY的值。找到并编辑go的配置文件(通常位于$HOME/.config/go/env),添加或修改以下行:

bash
GOPROXY=https://goproxy.cn,direct

编写文件

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

如果配置完代理,代码爆红

image-20240920213326969

这里就会出现包的引用

image-20240920213421086

运行代码

go
go run main.go

image-20240920212844050

访问路径

bash
http://localhost:8080

image-20240920212927670

1.4 常见的框架结结构

一般设计一个常规的web项目,都需要以下几个模块

runApp 主函数,运行整个项目 routes 路由控制,管理跳转以及路由分组 controllers 管理路由跳转后执行的逻辑 service/serviceImp 管理执行的具体业务,依赖注入时可以做实体接口分离 dao 管理数据库连接,数据库控制 filters 中间件,全局,路由分组中注入额外的执行方法,例如token,cors logs 日志系统,交给运维处理 configs 额外的属性配置,例如数据库连接信息,文件上传大小,线程池大小

2. 入门

下载测试api的工具-apifox

2.1 restful风格的代码

go
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 分组

分组

把同一个前缀的路由放在一起

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

启动的时候,会自动凭借上路由

image-20240921132304547

使用apifox进行访问

image-20240921132900642

image-20240921132911073

当项目变大的时候,一个main.go文件放上一大堆文件,这不合理

image-20240921133410358

image-20240921133456282

统一json返回

新建文件夹

image-20240921135054616

go
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

创建文件

image-20240921135334410

下面是代码

go
package controllers

import "github.com/gin-gonic/gin"

func GetUserInfo(c *gin.Context) {
	ReturnSuccess(c, 200, "success", "测试数据,使用的user", 0)
}

修改路由文件

image-20240921135546337

代码内容

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

测试的结果

image-20240921135624868

也可以换一种方式进行绑定

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

image-20240921143650462

这样子就不会发生方法名字冲突的问题

image-20240921143801025

go
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=北京

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

image-20240921144517610

Get-通过路径传递(Pathinfo)

/user/search/少林/北京

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

image-20240921145546981

POST-表单提交

POST /user/search/

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

测试内容

image-20240921150820836

POST-Json

为什么要参数绑定,本质上是方便,提高开发效率

A. 通过反射的机制,自动提取querystring、form表单、json、xml等参数到struct中

B. 通过http协议中的context type,识别是json、xml或者表单

json格式的数据,只能使用结构体或者map来接受

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

返回结果

image-20240921151339553

文件上传

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

多文件上传

go
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 等。

安装框架和连接驱动

bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

创建表

sql

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

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

下面的方法-是在模仿正规的项目,下面是目录结构

image-20240921164016350

创建配置文件

创建config文件夹,以及dao文件。分别放置配置文件以及,连接的文件

image-20240921155315211

go
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就使用这个了

image-20240921155059226

创建DAO

image-20240921160857349

go
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文件夹

创建实体类,首先查看数据库里面的字段

image-20240921161016509

go
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层

image-20240921164048931

go
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

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

这里

image-20240921163700593

goland每次都自动删除我import的包

第一步:取消Optimize imports on the fly勾选

img

第二步:取消Optimize imports

img

2.6 gorm的crud

整体的项目结构

image-20240922155922062

新增

在models下面新增一条方法

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

image-20240922153507237

在controller里面新增数据。ReturnError,ReturnSuccess是之前写的一个方法。可以换成其他的任意返回方法

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

image-20240922153529522

然后在routers.go里面添加路由

v2.POST("/add", controllers.CreateUser)

然后使用postman进行新增

image-20240922155411123

注意,表语句是自增的。如果不是自增的,json里面需要新增ID

go
-- 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 '邮箱'
);

修改

更新多个列

go
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层

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

路由新增

go
v2.GET("/update", controllers.UpdateUser)

先看一下原来的数据

image-20240922160030105

访问链接

image-20240922160053054

修改后的

image-20240922160130060

保存所有列

Save会保存所有的字段,即使字段是零值

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

更新某几个列

go
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;
}

删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:

go
func DeleteUser(id int) (int, error) {
	err := dao.Db.Where("id = ?", id).Delete(&User{Id: id}).Error
	return 1, err
}

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字

go
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 会执行批量删除,它将删除所有匹配的记录

go
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 方法,以便更高效的删除数据量大的记录

go
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 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误、

go
// 获取第一条记录(主键升序)
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

go
// 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条件

go
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);

排序

go
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

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

2.7 链接redis