gorm框架CRUD实践 | 豆包MarsCode AI刷题

291 阅读10分钟

写在前面:终于再次等到了青训营开营,在大一大二参加了两次却都因各种原因未能完成,大学快要结束之际再次迎来了青训营,也想在此次完成结营也算是了结遗憾。

在学习后端的过程中,我明白了一个道理,也不算道理,只能说是个人思考:学习开发本质上是在学习工具,开发的工作是使用各种工具去满足人们的需求,而计算机只是信息时代的新一代工具的聚集地,只要各位计算机学子能够有耐心的去学习并且掌握工具,便能轻松掌握如浙大翁恺老师所言:所谓计算机都是人类所开发出来的工具,既是人造而我亦能掌握。本文则介绍gorm框架的使用。 我将以现实需求的角度来实际引出工具的使用。

本次采用的例子为本人数据库大赛校赛的代码,源码入口:云竞赛后端

需求分析

背景

在信息化数字化盛行的年代,随着手机的普及很多事情都实现了网上办事,以一个大学生的角度而言,网上选课,网课(疫情时期懂的都懂)学习通签到(苦不堪言)等等,因此需求催生了很多工具。在很多学校设有在线科技竞赛平台,本文假设以建设一个科技竞赛管理平台为需求。

构建项目思路与框架

在确定要构建一个竞赛系统后,我们便开始确定项目需要什么功能,我们能否实现? 例如本文的目标为构建竞赛平台,先确定我们要实现什么功能:

  • 学生和老师都要能登录平台,故有了不同用户因此需要用户管理模块;
  • 竞赛需要能够发布、报名和删除等等则需要竞赛管路模块
  • 在不同的用户间应该有不同的权限,比如学生能够查看比赛,而教师或者管理员或者学生组织者需要能够管理比赛,管理员需要能够管理权限和密码等等 在这个过程中我们不仅仅是一个在线网页,所有的用户数据、比赛数据和权限等等都需要一个存储介质--数据库

golang的其中一个和数据库操作的工具便是--gorm 在我们新建、修改、删除用户和比赛等操作时,实则是对数据库信息的修改。此时gorm就发挥了他的作用:

gorm的基本使用

安装gorm和数据库驱动

首先我们要获取工具,我们可以通过命令下载,在goland中则可以直接图形化界面下载驱动即可

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

初始化

在安装环境之后下一步就是在整个系统的初始化中连接数据库

连接数据库

要能够实现和数据库实现通讯,我们首先需要建立连接

连接语法
import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

dsn := "username:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
参数详解
  • username:数据库用户名
  • password:数据库密码
  • host:数据库服务器的 IP 地址或域名
  • port:数据库服务器端口,MySQL 默认是 3306
  • dbname:要连接的数据库名称
  • charset=utf8mb4:设置字符编码,utf8mb4 支持表情符号
  • parseTime=True:将时间字段解析为 Go 的 time.Time 类型
  • loc=Local:设置时区为本地时区

实例

我的系统中我的数据库初始化是这样: 我的用户名是 root,密码是 123456,协议是 tcp,本地服务器mysql,数据库名称是 COMPETITION,需要的字符编码是 utf8mb4,、需要time.Time 类型,本地时区,则我的数据库连接代码为:

// InitDB 数据库初始化
func InitDB() {
    dsn := "root:123456@tcp(localhost:3306)/COMPETITION?charset=utf8mb4&parseTime=True&loc=Local"
    //各种错误处理
    ···
}

gorm结构体映射

gorm提供了简洁的数据库映射,它允许我们将数据库表与 Go 的结构体相对应,使得我们能够更方便地操作数据库。它不仅减少了手动编写 SQL 的工作量,还提高了代码的安全性和可维护性。借助结构体映射,开发者可以更专注于业务逻辑,而不是数据库操作细节。以下是一些区别:

对比

特性使用 GORM不使用 GORM (手动 SQL)
简洁性简洁高效,适合 CRUD代码冗长,开发效率较低
性能有一定性能开销更高效,适合高性能场景
可维护性更易维护,自动迁移手动管理,维护成本高
灵活性操作简单,复杂查询受限更灵活,适合复杂业务逻辑
安全性默认防 SQL 注入需手动防范 SQL 注入

使用方法

以下是常用的 GORM 标签参数的表格:

参数说明示例
column映射数据库中的列名gorm:"column:sid"
primaryKey设置该字段为主键gorm:"primaryKey"
size设置字段大小(通常用于 string 类型)gorm:"size:255"
not null设置字段为 NOT NULLgorm:"not null"
unique设置字段为唯一键gorm:"unique"
index设置字段为索引gorm:"index"
default设置字段的默认值gorm:"default:CURRENT_TIMESTAMP"
foreignKey设置外键字段(指定关联的字段)gorm:"foreignKey:SID"
references设置外键引用的目标字段gorm:"references:SID"
autoIncrement设置字段为自增字段(通常用于整数类型主键)gorm:"autoIncrement"
embedded嵌入式结构体,将嵌套结构体展平到当前结构体gorm:"embedded"
type设置字段的数据库类型(用于指定类型细节)gorm:"type:varchar(100)"
column设置数据库列名gorm:"column:sid"
ignore忽略该字段,不进行映射gorm:"-"
serializer设置该字段的自定义序列化方法gorm:"serializer:json"

实际运用

数据库表 students
ColumnTypeDescription
sidVARCHAR(50)主键,学生ID
nameVARCHAR(255)学生姓名
passwordVARCHAR(255)学生密码
sexINT性别,1为男,0为女
gradeINT年级
classVARCHAR(50)班级
role_idINT角色ID,外键
create_timeTIMESTAMP创建时间,默认当前时间
update_timeTIMESTAMP更新时间,默认当前时间
对应的GORM 结构体映射
type Students struct {
	SID        string    `gorm:"column:sid;primaryKey" json:"sid"`            // 学生ID,主键
	Name       string    `gorm:"size:255;not null" json:"name"`               // 学生姓名,非空
	Password   string    `gorm:"size:255;not null" json:"password"`           // 密码,非空
	Sex        *int      `gorm:"not null" json:"sex"`                         // 性别,指针类型,便于表示0代表女生
	Grade      int       `gorm:"not null" json:"grade"`                       // 年级,非空
	Class      string    `gorm:"size:255;not null" json:"class"`              // 班级,非空
	RoleID     int       `gorm:"index" json:"role_id"`                        // 角色ID,索引
	CreateTime time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"create_time"` // 创建时间,默认当前时间
	UpdateTime time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"update_time"` // 更新时间,默认当前时间
}

注:结构体字段的名字通常会映射到表中的列(字段名自动转换为蛇形命名法,假如结构体字段名为 UserName,GORM 会将其映射为 user_name 列名),因此比如本例中的CreateTime映射应该是create_time。但单一的大写字母组合SID类的则会转换为全小写sid

映射到数据库表

GORM 会在你运行 AutoMigrate 时自动映射结构体与数据库表之间的关系,示例代码如下:

// 数据库初始化
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    panic("数据库连接失败")
}

// 自动迁移数据库结构
db.AutoMigrate(&Students{})

这段代码会自动根据 Students 结构体创建或更新数据库中的 students 表。如果 students 表已经存在,GORM 会确保表结构与结构体中的字段一致,包括列的类型、主键、索引等。

通过结构体操作数据库(CRUD)

Create(创建)

student := Students{
    SID:     "S12345",
    Name:    "John Doe",
    Password: "password123",
    Sex:     new(int), // 假设性别为男
    Grade:   3,
    Class:   "Class A",
    RoleID:  1,
}
db.Create(&student)

Read(读取)

以学生查询为例,系统实现可以部分片段查询

  1. 初始化查询

    query := config.DB.Model(&models.Students{})
    

    这一行初始化了一个 GORM 查询对象,基于 Students 模型进行查询。config.DB 是数据库连接,Model(&models.Students{}) 指定了查询的表是 Students 对应的数据库表。 -Model(&models.Students{}) 设置了查询的目标是 Students 表,后续的查询条件都基于该表。

  2. 根据查询参数动态添加条件 接下来,根据不同的查询条件(比如学生的名字、班级等),逐步构建 SQL 查询。

if queryParams.Name != "" {
    query = query.Where("name LIKE ?", "%"+queryParams.Name+"%")
}
if queryParams.Class != "" {
    query = query.Where("class LIKE ?", "%"+queryParams.Class+"%")
}

其中姓名和班级可以不完整查询,生成数据库语句便是

SELECT * FROM students WHERE name LIKE '%queryParams.Name%' AND class LIKE'%queryParams.Class%'

如果其中某个参数为空,则不会加上相应的 WHERE 子句。例如,如果只有 queryParams.Name 不为空,生成的 SQL 查询语句将是:

SELECT * FROM students WHERE name LIKE '%queryParams.Name%'

其他的如性别、学号和年级查询则为完整字段查询:

if queryParams.SID != "" {
    query = query.Where("sid = ?", queryParams.SID)
}
if queryParams.Sex != nil {
    query = query.Where("sex = ?", *queryParams.Sex)
}
if queryParams.Grade != 0 {
    query = query.Where("grade = ?", queryParams.Grade)
}

生成的sql语句为

SELECT * FROM students WHERE sid = 'queryParams.SID' AND sex = queryParams.Sex AND grade = queryParams.Grade

其中条件为空则舍弃

查询构建流程
  • 初始时,查询对象 query 是基于 Students 表的,接着每次根据不同的 queryParams 参数添加查询条件。
  • 这段代码最终的查询会基于传入的参数动态组合不同的条件,通过 Where 方法将多个条件合并。
  • Where 方法的使用让查询更具灵活性,可以根据条件的有无动态调整查询内容。
最终执行查询

构建好查询条件后,通常会通过 query.Find(&results)query.First(&result) 等方法执行查询,获取结果。例如:

var students []models.Students
query.Find(&students)

这样,查询会根据所有的条件(如果有的话)来筛选学生数据,并将结果存入 students 切片中。

Update(更新)

在 GORM 中,Update 操作主要涉及 ModelWhereUpdates 等方法。这些方法帮助我们指定更新的对象、条件和需要更新的字段。

更新角色信息
tx.Model(&models.Roles{}).Where("id = ?", data.ID).Updates(models.Roles{Label: data.Label, Description: data.Description})
  • Model(&models.Roles{})
    这部分指定了要操作的表为 Roles。它告诉 GORM 接下来的操作是在 Roles 表上进行的。
  • Where("id = ?", data.ID) Where 方法设置了查询的条件,即通过 id 查找要更新的记录。? 是一个占位符,后面的 data.ID 会替换掉 ?,从而生成类似于 WHERE id = 1 这样的 SQL 条件。
  • Updates(models.Roles{Label: data.Label, Description: data.Description}) Updates 方法用来指定需要更新的字段。在这里,LabelDescription 字段会被更新为 data.Labeldata.Description 的值。如果不指定字段,默认会更新所有字段。 生成的 SQL 语句类似于:
UPDATE roles SET label = ?, description = ? WHERE id = ?

Delete(删除)

// 删除学号为 S12345 的学生
db.Where("sid = ?", "S12345").Delete(&Students{})

总结

GORM的使用为数据库操作提供了极大的便利:

  1. 简洁高效:GORM通过结构体映射的方式,简化了数据库表与Go语言结构体之间的对应关系,减少了手动编写SQL的工作量,使得CRUD操作更加简洁、直观。相比手写SQL,它不仅提高了开发效率,还减少了出错的机会。
  2. 灵活与安全性: GORM提供了自动迁移和防SQL注入的功能,减少了数据库管理和维护的负担。通过使用GORM的标签功能,可以精确控制字段映射和约束,如设置字段为主键、唯一、外键等,并且能够自动管理数据库表结构的变更。
  3. 数据库操作:通过GORM,可以轻松实现对数据库的增、查、改、删等操作。例如,在查询过程中,我们不仅能够使用灵活的条件查询,还能动态拼接SQL条件,提高了查询的效率和可维护性。

学习开发的关键在于运用工具,熟练使用各项工具,提升自我,早日成为大牛