五、Gorm
5.1、gorm介绍
是当今比较热门的 golang 的 orm 操作数据库的工具,使用上主要是把 struct 类和数据库表进行映射,操作数据库时无需手写 sql。本质就是提供一组函数来帮助我们快速拼接 sql 语句,尽量减少 sql 的编写。
和
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
5.2、连接数据库
5.2.1、连接数据库
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "user:pwd@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println(db, err)
}
5.2.2、配置日志连接
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
// 创建日志对象
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second, // 慢SQL阈值
LogLevel: logger.Info, // log level
},
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger, // 日志配置
})
if err != nil {
panic("failed to connect database")
}
fmt.Println(db)
}
// 全局数据库 db
var db *gorm.DB
// 包初始化函数,可以用来初始化 gorm
func init() {
// 配置 dsn
// err
var err error
// 连接 mysql 获取 db 实例
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
// 设置数据库连接池参数
sqlDB, _ := db.DB()
// 设置数据库连接池最大连接数
sqlDB.SetMaxOpenConns(100)
// 连接池最大允许的空闲连接数,如果没有sql任务需要执行的连接数大于20,超过的连接会被连接池关闭
sqlDB.SetMaxIdleConns(20)
}
// 获取 gorm db,其他包调用此方法即可拿到 db
// 无需担心不同协程并发时使用这个 db 对象会公用一个连接,因为 db 在调用其方法时候会从数据库连接池获取新的连接
func GetDB() *gorm.DB {
return db
}
func main() {
// 获取 db
db := tools.GetDB()
// 执行数据库查询操作
}
5.3.1、模型声明
我们以选课系统为例子:
学生表
老师表
班级表
课程表
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type BaseModel struct {
ID int `gorm:"primaryKey"`
CreateTime *time.Time `gorm:"autoCreateTime"`
UpdateTime *time.Time `gorm:"autoCreateTime"`
Name string `gorm:"type:varchar(32);unique;not null"`
}
type Teacher struct {
BaseModel
Tno int
Pwd string `gorm:"type:varchar(100);not null"`
Tel string `gorm:"type:char(11);"`
Birth *time.Time
Remark string `gorm:"type:varchar(255);"`
}
type Class struct {
BaseModel
Num int
TutorID int
Tutor Teacher
}
type Course struct {
BaseModel
Credit int
Period int
// 多对一
TeacherID int
Teacher Teacher
}
type Student struct {
BaseModel
Sno int
Pwd string `gorm:"type:varchar(100);not null"`
Tel string `gorm:"type:char(11);"`
Gender byte `gorm:"default:1"`
Birth *time.Time
Remark string `gorm:"type:varchar(255);"`
// 多对一
ClassID int
Class Class `gorm:"foreignKey:ClassID"`
// 多对多
Courses []Course `gorm:"many2many:student2course;constraint:OnDelete:CASCADE;"`
}
默认情况下,GORM 使用
ID
作为主键,使用结构体名的蛇形复数
作为表名,字段名的蛇形
作为列名,并使用CreatedAt
、UpdatedAt
字段追踪创建、更新时间。单独设置表名:
func (u User) TableName() string { return "user" }
GORM 定义一个
gorm.Model
结构体,其包括字段ID
、CreatedAt
、UpdatedAt
、DeletedAt
// gorm.Model 的定义 type Model struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` }
5.3.2、模型迁移
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
// 创建日志对象
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second, // 慢SQL阈值
LogLevel: logger.Info, // log level
},
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger, // 日志配置
})
if err != nil {
panic("failed to connect database")
}
// 自动迁移
db.AutoMigrate(&Teacher{})
db.AutoMigrate(&Course{})
db.AutoMigrate(&Class{})
db.AutoMigrate(&Student{})
}
AutoMigrate支持建表,,如果表存在则不会再创建
-- 建表
db.AutoMigrate(&Teacher{})
-- 建表
db.Migrator().CreateTable(&Teacher{})
-- 建 3 表
db.AutoMigrate(&Teacher{}, &Class{}, &Course{})
-- 可以通过 Set 设置附加参数,下面设置表的存储引擎为 InnoDB
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&Teacher{})
检测表存在:
// 检测Teacher结构体对应的表是否存在
db.Migrator().HasTable(&Teacher{})
// 检测表名teachers是否存在
db.Migrator().HasTable("teachers")
5.3.3、表字段操作
AutoMigrate不支持字段修改删除,为了便秘数据意外丢失。
-- 删除 Teacher 结构体对应的表
db.Migrator().DropTable(&Teacher{})
-- 依据表名删除表
db.Migrator().DropTable("teachers")
-- 删除字段
db.Migrator().DropColumn(&Teacher{}, "Name")
-- 为字段添加索引
db.Migrator().CreateIndex(&Teacher{}, "Name")
-- 修改索引名
db.Migrator().RenameIndex(&Teacher{}, "Name", "Name2")
-- 为字段删除索引
db.Migrator().DropIndex(&Teacher{}, "Name")
-- 检查索引存在
db.Migrator().HasIndex(&Teacher{}, "Name")
5.4、数据库表记录操作
5.4.1、添加记录
(1)添加单表记录
t1 := Teacher{BaseModel: BaseModel{Name: "hao"}, Tno: 1001, Pwd: "123"}
db.Create(&t1)
fmt.Println(t1.ID)
t2 := Teacher{BaseModel: BaseModel{Name: "echo"}, Tno: 1002, Pwd: "234"}
db.Create(&t2)
t3 := Teacher{BaseModel: BaseModel{Name: "June"}, Tno: 1002, Pwd: "345"}
db.Create(&t3)
GORM 将生成一条 SQL 语句来插入数据并回填主键值
db.Debug()
会打印sql日志
(2)批量添加多对一关联表的记录
要有效地插入大量记录,请将切片传递给该Create方法。
class01 := Class{BaseModel: BaseModel{Name: "软件1班"}, TutorID: 1}
class02 := Class{BaseModel: BaseModel{Name: "软件2班"}, TutorID: 1}
class03 := Class{BaseModel: BaseModel{Name: "计算机科学与技术1班"}, TutorID: 2}
class04 := Class{BaseModel: BaseModel{Name: "计算机科学与技术2班"}, TutorID: 2}
classes := []Class{class01, class02, class03, class04}
db.Create(&classes)
for _, class := range classes {
fmt.Println(class.ID) // 1,2,3
}
course01 := Course{BaseModel: BaseModel{Name: "计算机网络"}, Credit: 3, Period: 16, TeacherID: 1}
course02 := Course{BaseModel: BaseModel{Name: "数据结构"}, Credit: 2, Period: 24, TeacherID: 1}
course03 := Course{BaseModel: BaseModel{Name: "数据库"}, Credit: 2, Period: 16, TeacherID: 2}
course04 := Course{BaseModel: BaseModel{Name: "数字电路"}, Credit: 3, Period: 12, TeacherID: 2}
course05 := Course{BaseModel: BaseModel{Name: "模拟电路"}, Credit: 1, Period: 8, TeacherID: 2}
courses := []Course{course01, course02, course03, course04, course05}
db.Create(&courses)
指定批量大小:db.CreateInBatches(users, 100 )
(3)添加多对多关联表的记录
// 绑定课程对象切片
var courses []Course
db.Where("name in ?", []string{"数据结构", "数据库"}).Find(&courses)
fmt.Println("courses:", courses)
// 添加学生1
s1 := Student{BaseModel: BaseModel{Name: "张三"}, Sno: 2001, Pwd: "123", ClassID: 1}
db.Create(&s1)
// 多对多添加方式1
s2 := Student{BaseModel: BaseModel{Name: "李四"},
Sno: 2002,
Pwd: "234",
ClassID: 1,
Courses: courses,
}
db.Create(&s2)
// 多对多添加方式2
s3 := Student{BaseModel: BaseModel{Name: "王五"}, Sno: 2003, Pwd: "234", ClassID: 1}
db.Create(&s3)
fmt.Println("s3 id:", s3.ID)
db.Model(&s3).Association("Courses").Append(courses) // 注意:Courses是多对多关联字段,不是关联表
// 先查询再操作
var student = Student{}
db.Where("name = ?", "王五").First(&student)
fmt.Println(student)
db.Model(&student).Association("Courses").Clear()
var courses []Course
db.Where("name in ?", []string{"数字电路", "模拟电路"}).Find(&courses)
db.Model(&student).Association("Courses").Append(courses)
5.4.2、单表查询
gorm 使用链式函数来查询的。
(1) 查询全部记录
// Find 查询多条记录,返回数组
// select * from students;
students := []Student{} // 使用Find要声明数组,如果声明一个对象(var s Student),就会将第一个对象
result := db.Find(&students)
fmt.Println(result.RowsAffected)
fmt.Println(students)
for _, student := range students {
fmt.Println(student.ID, student.Name, student.Sno)
}
/*
students := []Student{}
(1) select * from students;
5,2022-11-29 16:53:23.823,2022-11-29 16:53:23.823,张三,2001,123,"",1,2022-11-15 00:00:00,"",1,1
6,2022-11-29 16:53:50.756,2022-11-29 16:53:50.756,李四,2002,123,"",1,2022-11-10 00:00:00,"",3,2
7,2022-11-29 16:54:11.471,2022-11-29 16:54:11.471,王五,2003,123,"",1,2022-11-18 00:00:00,"",2,3
(2)
[
Student{
BaseModel: {1 2022-11-03 17:59:02.188 +0800 CST 2022-11-03 17:59:02.188 +0800 CST 张三}
Sno: 2001
Pwd: 123
Gender: 1
Birth: <nil>
ClassID: 1
Class对象: {
BaseModel: {0 <nil> <nil> }
Num: 0
TutorID: 0
Tutor对象: {{0 <nil> <nil> } 0 <nil>}
students: []
}
Courses: []
},
...
]
*/
(2)查询单条记录
student := Student{}
// Take 查询一条记录
// SELECT * FROM `user` LIMIT 1
db.Take(&student)
// First 根据主键 id 排序后的第一条
// SELECT * FROM `user` ORDER BY `id` LIMIT 1
db.First(&student)
// Last 根据主键 id 排序后最后一条
// SELECT * FROM `user` ORDER BY `id` DESC LIMIT 1
db.Last(&student)
// Where 表示条件,其中写 sql 部分
// SELECT * FROM `user` WHERE (id = '10') LIMIT 1
db.Where("id = ?", 10).Take(&user)
(3) Where查询
基于string的where语句
var student Student
db.Where("name = ?", "李四").First(&student)
fmt.Println(student)
var students []Student
db.Where("Sno between ? and ?", 2001, 2003).Find(&students)
fmt.Println(students)
var students []Student
db.Where("Sno in ?", []int64{2001, 2003}).Find(&students)
fmt.Println(students)
var students []Student
db.Where("name like ?", "李%").Find(&students)
fmt.Println(students)
var students []Student
db.Where("create_time > ?", "2021-01-01 00:00:00").Find(&students)
fmt.Println(students)
var students []Student
db.Where("create_time > ? AND create_time < ?", "2022-01-01 00:00:00", "2022-12-31 00:00:00").Find(&students)
fmt.Println(students)
基于Struct & Map 条件的where语句
// Struct条件
var students []Student
db.Where(&Student{BaseModel: BaseModel{Name: "张三"}, Gender: 1}).Find(&students)
fmt.Println(students)
// 注意: 使用结构作为条件查询时,GORM 只会查询非零值字段。例如:
db.Where(&Student{BaseModel: BaseModel{Name: "张三"}, Gender: 0}).Find(&students)
fmt.Println(students)
// SELECT * FROM `students` WHERE `students`.`name` = '张三'
// Map条件
db.Where(map[string]interface{}{"Name": "张三", "Gender": 0}).Find(&students)
fmt.Println(students)
(4)其他查询语句
-- Select语句 表示选择,其中写 sql 部分
-- SELECT name,sno FROM `students` WHERE id = 10 LIMIT 1
var student Student
db.Select("name,sno").Where("id = ?", 10).Take(&student)
log.Println(student)
db.Omit("name", "sno").Find(&students)
fmt.Println(students)
-- Not语句
var students []Student
db.Not("sno between ? and ?", 2001, 2002).Find(&students) -- Not语句:用法类似于Where
fmt.Println(students)
-- Or语句
var students []Student
db.Where("sno = ?", 2001).Or("name like ?", "王%").Find(&students)
fmt.Println(students)
-- Order 表示排序方式,其中写 sql 部分
-- SELECT * FROM `students` WHERE create_time >= '2018-11-06 00:00:00' ORDER BY create_time desc,id
var students []Student
db.Where("create_time >= ?", "2018-11-06 00:00:00").Order("create_time desc,id").Find(&students)
log.Println(students)
-- Limit Offset 分页常用
--SELECT * FROM `students` ORDER BY create_time desc LIMIT 10 OFFSET 1
var students []Student
db.Order("create_time desc").Limit(10).Offset(1).Find(&students)
log.Println(students)
-- Count 计算行数
-- SELECT count(*) FROM `students`
var total int64
db.Model(Student{}).Count(&total)
fmt.Println(total)
-- Group Having 分组查询,其中写 sql 部分,Group 必须和 Select 一起连用
type Result struct {
ClassID int
Total int
}
var results []Result
-- SELECT class_id, Count(*) as total FROM `students` GROUP BY `class_id` HAVING total>1
db.Model(Student{}).Select("class_id,Count(*) astotal").Group("class_id").Having("total>0").Scan(&results)
log.Println(results)
5.4.3、删除记录
// 删除一条记录
student := Student{BaseModel: BaseModel{ID: 3}}
db.Delete(&student)
// 按条件删除
db.Where("sno between ? and ?", 2001, 2002).Delete(Student{})
// 删除所有记录
db.Where("1 = 1").Delete(&Student{})
// Save 更新某条记录的所有字段
stu01 := Student{}
db.First(&stu01)
stu01.Name = "张三三"
db.Save(&stu01)
// Update 基于主键更新某条记录的单个字段
stu02 := Student{BaseModel: BaseModel{ID: 1}}
db.Model(&stu02).Update("name", "张三")
// Update 跟新所有记录的单个字段
db.Model(&Student{}).Update("price", 25)
// Update 自定义条件而非主键记录更新某字段
db.Model(&Student{}).Where("create_time > ?", "2018-11-06 20:00:00").Update("price", 25)
// Update 更新多个字段
// 通过 `struct` 更新多个字段,不会更新零值字段
db.Model(&Student{}).Where("id = ?", 2).Updates(Student{Sno: 2002, Gender: 0})
// 通过 `map` 更新多个字段,零值字段也会更新
db.Model(&Student{}).Where("id = ?", 2).Updates(map[string]interface{}{"gender": 1, "sno": 2002})
// 更新表达式
Update("stock", gorm.Expr("stock + 1"))
db.Model(&Class{}).Update("Num", gorm.Expr("Num+1"))
db.Model(&Student{}).Update("Pwd", gorm.Expr("Sno"))
5.4.5、关联表查询
(1)Preload(子查询 )
GORM 允许在 Preload
的其它 SQL 中直接加载关系
案例1:查询李四的班级的名称
// 手动查询
s := Student{}
db.Where("name = ?", "李四").Find(&s)
fmt.Println(s)
class := Class{}
db.Where("id = ?", s.ClassID).Find(&class)
fmt.Println(class.Name)
// Preload 预加载
s := Student{}
db.Where("name = ?", "李四").Preload("Class").Find(&s)
// SELECT * FROM `students` WHERE name = '李四'
// SELECT * FROM `classes` WHERE `classes`.`id` = 1
fmt.Println(s)
fmt.Println(s.Sno)
fmt.Println(s.Class.Name)
fmt.Println(s.Courses)
案例2:查询张三的班级和所选课程
s := Student{}
db.Where("name = ?", "李四").Preload("Class").Preload("Courses").Find(&s)
// "gorm.io/gorm/clause"
// db.Where("name = ?", "lisi").Preload(clause.Associations).Find(&s)
// SELECT * FROM `students` WHERE name = '李四'
// SELECT * FROM `classes` WHERE `classes`.`id` = 2
// SELECT * FROM `student2course` WHERE `student2course`.`student_id` = 11
// SELECT * FROM `courses` WHERE `courses`.`id` IN (2,3)
fmt.Println(s)
fmt.Println(s.Sno)
fmt.Println(s.Class.Name)
fmt.Println(s.Courses)
fmt.Println(s.Courses[1])
案例3:查询软件一班所有学生【反向查询】
// class表添加成员变量:Students
type Class struct {
BaseModel
Num int
TutorID int
Tutor Teacher
// 一对多
Students []Student // 反向查询字段
}
class := Class{}
db.Where("name", "软件1班").Preload("Students").Find(&class)
// SELECT * FROM `classes` WHERE `name` = '软件1班'
// SELECT * FROM `students` WHERE `students`.`class_id` = 1
fmt.Println(class)
fmt.Println(class.Students)
for _, stu := range class.Students {
fmt.Println(stu.ID,stu.Name)
}
案例4:查询软件一班所有学生以及所学课程【嵌套预加载】
class := Class{}
db.Where("name", "软件1班").Preload("Students.Courses").Find(&class)
fmt.Println(class)
fmt.Println(class.Students)
for _, stu := range class.Students {
fmt.Println(stu.ID, stu.Name, stu.Courses)
}
// Course表添加成员变量:Students
type Course struct { BaseModel Credit int8 Period int8 // 多对一 TeacherID int Teacher Teacher // 多对多 Students []Student `gorm:"many2many:student2course;"` }
course := Course{}
db.Where("name", "数据结构").Preload("Students").Find(&course)
// db.Where("name", "数据结构").Preload("Students.Courses").Find(&course)
fmt.Println(course)
fmt.Println(course.Students)
for _, stu := range course.Students {
fmt.Println(stu.ID, stu.Name, stu.Courses)
}
(2)Joins 查询
在一个单独查询中加载关联数据。而 Join Preload
会使用 inner join 加载关联数据
案例1:查询李四的班级名称【一对多】
var stu = Student{}
db.Where("students.name = ?", "李四").Joins("Class").Find(&stu)
fmt.Println(stu)
fmt.Println(stu.Class.Name)
案例2:查询李四的所选课程【多对多】
stu := Student{BaseModel: BaseModel{ID: 2}}
var courses []Course
db.Model(&stu).Association("Courses").Find(&courses)
fmt.Println(courses)
/*
SELECT `courses`.`id`,`courses`.`create_time`,`courses`.`update_time`,`courses`.`name`,`courses`.`credit`,`courses`.`period`,`courses`.`teacher_id`
FROM `courses` JOIN `student2course`
ON `student2course`.`course_id` = `courses`.`id`
AND `student2course`.`student_id` = 2
*/
//db.Model() 中必须是ID筛选
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2022-07-17 1.WEB应用模式