写在前面:终于再次等到了青训营开营,在大一大二参加了两次却都因各种原因未能完成,大学快要结束之际再次迎来了青训营,也想在此次完成结营也算是了结遗憾。
在学习后端的过程中,我明白了一个道理,也不算道理,只能说是个人思考:学习开发本质上是在学习工具,开发的工作是使用各种工具去满足人们的需求,而计算机只是信息时代的新一代工具的聚集地,只要各位计算机学子能够有耐心的去学习并且掌握工具,便能轻松掌握如浙大翁恺老师所言:所谓计算机都是人类所开发出来的工具,既是人造而我亦能掌握。本文则介绍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 NULL | gorm:"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
| Column | Type | Description |
|---|---|---|
| sid | VARCHAR(50) | 主键,学生ID |
| name | VARCHAR(255) | 学生姓名 |
| password | VARCHAR(255) | 学生密码 |
| sex | INT | 性别,1为男,0为女 |
| grade | INT | 年级 |
| class | VARCHAR(50) | 班级 |
| role_id | INT | 角色ID,外键 |
| create_time | TIMESTAMP | 创建时间,默认当前时间 |
| update_time | TIMESTAMP | 更新时间,默认当前时间 |
对应的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(读取)
以学生查询为例,系统实现可以部分片段查询
-
初始化查询
query := config.DB.Model(&models.Students{})这一行初始化了一个 GORM 查询对象,基于
Students模型进行查询。config.DB是数据库连接,Model(&models.Students{})指定了查询的表是Students对应的数据库表。 -Model(&models.Students{})设置了查询的目标是Students表,后续的查询条件都基于该表。 -
根据查询参数动态添加条件 接下来,根据不同的查询条件(比如学生的名字、班级等),逐步构建 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 操作主要涉及 Model、Where 和 Updates 等方法。这些方法帮助我们指定更新的对象、条件和需要更新的字段。
更新角色信息
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方法用来指定需要更新的字段。在这里,Label和Description字段会被更新为data.Label和data.Description的值。如果不指定字段,默认会更新所有字段。 生成的 SQL 语句类似于:
UPDATE roles SET label = ?, description = ? WHERE id = ?
Delete(删除)
// 删除学号为 S12345 的学生
db.Where("sid = ?", "S12345").Delete(&Students{})
总结
GORM的使用为数据库操作提供了极大的便利:
- 简洁高效:GORM通过结构体映射的方式,简化了数据库表与Go语言结构体之间的对应关系,减少了手动编写SQL的工作量,使得CRUD操作更加简洁、直观。相比手写SQL,它不仅提高了开发效率,还减少了出错的机会。
- 灵活与安全性: GORM提供了自动迁移和防SQL注入的功能,减少了数据库管理和维护的负担。通过使用GORM的标签功能,可以精确控制字段映射和约束,如设置字段为主键、唯一、外键等,并且能够自动管理数据库表结构的变更。
- 数据库操作:通过GORM,可以轻松实现对数据库的增、查、改、删等操作。例如,在查询过程中,我们不仅能够使用灵活的条件查询,还能动态拼接SQL条件,提高了查询的效率和可维护性。
学习开发的关键在于运用工具,熟练使用各项工具,提升自我,早日成为大牛