GORM堪称是一款设计极为简洁,同时功能又十分强大,并且还能够自由进行扩展的全功能ORM。其秉持着诸多优秀的设计原则,像是秉持“API精简”的理念,力求让接口简洁易用;坚持“测试优先”,以确保各项功能的稳定性与可靠性;遵循“最小惊讶”原则,让使用者在使用过程中不会遇到太多意外情况;具备“灵活扩展”的特性,可根据不同需求轻松拓展功能;做到“无依赖”,使其能在各种环境下独立运行且便于集成;并且凭借“可信赖”的特质,赢得了众多开发者的信任。
在此基础上,GORM还提供了一系列丰富且完善的功能:
关联方面:
- 涵盖了多种关联类型,包括一对一、一对多、单表自关联以及多态等关联形式。
- 支持Preload、Joins预加载操作,还能实现级联删除功能。
- 具备关联模式,可满足不同场景下的关联需求。
- 允许用户自定义关联表,进一步增强了关联的灵活性。
事务相关:
- 提供了事务代码块功能,方便进行事务处理操作。
- 支持嵌套事务,以应对更为复杂的业务逻辑场景。
- 具备Save Point功能,可在事务处理过程中设置保存点。
数据库操作层面:
- 能够适配多数据库环境,实现读写分离操作。
- 支持命名参数、Map等多种数据处理方式。
- 可进行子查询、分组条件设置等操作,还能实现代码共享。
- 支持SQL表达式,涵盖查询、创建、更新等多种操作类型,并且配备了自动选字段以及查询优化器,提升查询效率。
字段相关特性:
- 可设置字段权限,保障数据的安全性与合理性。
- 支持软删除功能,方便数据的管理与恢复操作。
- 具备批量数据处理能力,提高数据处理效率。
- 支持Prepared Stmt,提升数据操作的性能。
- 允许自定义类型,满足多样化的数据类型需求。
- 提供命名策略,规范数据的命名方式。
- 设有虚拟字段,丰富数据的呈现形式。
- 可自动track时间,便于数据的时间记录与追溯。
- 配备SQL Builder和Logger,方便进行SQL语句的构建与日志记录。
其他功能:
- 具备代码生成功能,提高开发效率。
- 支持复合主键、Constraint等数据库约束设置。
- 可与Prometheus进行集成,便于监控相关数据。
- 拥有Auto Migration功能,实现数据库的自动迁移操作。
- 真正做到了跨数据库兼容,可在不同数据库之间无缝切换使用。
以一个学生表为例构建学习CRUD
mysql表命令
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS gorm_student;
-- 使用创建好的数据库
USE gorm_student;
-- 创建students表
CREATE TABLE IF NOT EXISTS students (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
gender ENUM('male', 'female', 'other') NOT NULL,
age TINYINT UNSIGNED NOT NULL,
birthday VARCHAR(255),
class_id BIGINT UNSIGNED NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
deleted_at TIMESTAMP
);
-- 创建classes表
CREATE TABLE IF NOT EXISTS classes (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
teacher_id BIGINT UNSIGNED NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
deleted_at TIMESTAMP
);
-- 插入班级数据示例
INSERT INTO classes (name, teacher_id, created_at, updated_at, deleted_at)
VALUES
('一班', 1, '2024-11-25 17:00:00', '2024-11-25 17:00:00', NULL),
('二班', 2, '2024-11-25 17:05:00', '2024-11-25 17:05:00', NULL),
('三班', 3, '2024-11-25 17:10:00', '2024-11-25 17:10:00', NULL);
-- 创建teachers表
CREATE TABLE IF NOT EXISTS teachers (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
subject VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
deleted_at TIMESTAMP
);
-- 插入教师数据示例
INSERT INTO teachers (name, subject, created_at, updated_at, deleted_at)
VALUES
('张老师', '数学', '2024-11-25 17:20:00', '2024-11-25 17:20:00', NULL),
('李老师', '语文', '2024-11-25 17:25:00', '2024-11-25 17:25:00', NULL),
('王老师', '英语', '2024-11-25 17:30:00', '2024-11-25 17:30:00', NULL);
entity
package entity
import (
"gorm.io/gorm"
)
// Student结构体,对应students表
type Student struct {
gorm.Model
Name string `gorm:"not null" json:"name"`
Gender string `gorm:"type:enum('male', 'female', 'other');not null" json:"gender"`
Age uint8 `gorm:"not null" json:"age"`
Birthday string `json:"birthday,omitempty"`
ClassID uint64 `gorm:"not null" json:"class_id"`
}
// Class结构体,对应classes表
type Class struct {
gorm.Model
Name string
TeacherID uint64 `gorm:"not null"`
}
// Teacher结构体,对应teachers表
type Teacher struct {
gorm.Model
Name string
Subject string `gorm:"not null"`
}
这里我们用了gorm.Model,我们来看看它有什么
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
实际上包含了我们所需要的东西,因此我们可以直接写成成员变量。
CRUD接口使用
创建
package main
import (
"GormDemo/entity"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
mysqlConnect := "root:000000@tcp(192.168.23.233:3306)/gorm_student?charset=utf8mb4&parseTime=True&loc=Local"
//GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.23.1' IDENTIFIED BY '000000' WITH GRANT OPTION;
mysqlDialector := mysql.New(mysql.Config{
DSN: mysqlConnect,
DefaultStringSize: 256,
})
db, err := gorm.Open(mysqlDialector, &gorm.Config{})
if err != nil {
fmt.Println(err.Error())
}
s1 := &entity.Student{
Name: "cqq",
Age: 24,
Birthday: "2000-05-05",
ClassID: 1,
Gender: "male",
}
result := db.Create(&s1)
fmt.Println("Current Result>", result.RowsAffected)
}
运行结果
Current Result> 1
这时候我们去查询数据库
mysql> select * from students;
+----+------+--------+-----+------------+----------+---------------------+---------------------+------------+
| id | name | gender | age | birthday | class_id | created_at | updated_at | deleted_at |
+----+------+--------+-----+------------+----------+---------------------+---------------------+------------+
| 1 | cqq | male | 24 | 2000-05-05 | 1 | 2024-11-25 16:32:56 | 2024-11-25 16:32:56 | NULL |
+----+------+--------+-----+------------+----------+---------------------+---------------------+------------+
1 row in set (0.00 sec)
用指定的字段创建记录
s1 := &entity.Student{
Name: "test2",
Age: 24,
ClassID: 1,
}
result := db.Select("name", "age", "class_id").Create(&s1)
批量插入
我们可以通过一个数组的方式,通过Create()来执行插入
studentsInsert := []entity.Student{
{
Name: "test3",
Age: 1,
ClassID: 2,
},
{
Name: "test4",
Age: 2,
ClassID: 1,
},
{
Name: "test5",
Age: 2,
ClassID: 3,
},
}
result := db.Select("name", "age", "class_id").Create(studentsInsert)
结果如下:
查询
Find查询
var students []entity.Student
find := db.Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
运行结果
Find> &{<nil> 0xc000094360}
StudentName: cqq
ClassID: 1
StudentName: test2
ClassID: 1
StudentName: test3
ClassID: 2
StudentName: test4
ClassID: 1
StudentName: test5
ClassID: 3
StudentName: test3
ClassID: 2
StudentName: test4
ClassID: 1
StudentName: test5
ClassID: 3
当我们在Create()中不传入任何值时,就可以检索所有的对象。
Where带条件查询
var students []entity.Student
find := db.Where("name", "test2").Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
我们看看Where说明
// Where add conditions
//
// See the [docs] for details on the various formats that where clauses can take. By default, where clauses chain with AND.
//
// // Find the first user with name jinzhu
// db.Where("name = ?", "jinzhu").First(&user)
// // Find the first user with name jinzhu and age 20
// db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// // Find the first user with name jinzhu and age not equal to 20
// db.Where("name = ?", "jinzhu").Where("age <> ?", "20").First(&user)
//
// [docs]: https://gorm.io/docs/query.html#Conditions
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) {
tx = db.getInstance()
if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: conds})
}
return
}
使用的方式如示例的三个所示,是泛型的方式传入到这个函数里的,也就说我们可以在里面做的事情就多了。
按官方给的用例
// Find the first user with name jinzhu
db.Where("name = ?", "jinzhu").First(&user)
// Find the first user with name jinzhu and age 20
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// Find the first user with name jinzhu and age not equal to 20
db.Where("name = ?", "jinzhu").Where("age <> ?", "20").First(&user)
我们可以按照和SQL一样的风格的写法,也可以像样例2一样传入一个指定了字段值的对象。
like功能
// 查询
var students []entity.Student
find := db.Where("name like ?", "test%").Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
运行结果
Find> &{<nil> 0xc00020a360}
StudentName: test2
ClassID: 1
StudentName: test3
ClassID: 2
StudentName: test4
ClassID: 1
StudentName: test5
ClassID: 3
StudentName: test3
ClassID: 2
StudentName: test4
ClassID: 1
StudentName: test5
ClassID: 3
Order排序
// 查询
var students []entity.Student
find := db.Where("name like ?", "test%").Order("class_id").Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
运行结果
Find> &{<nil> 0xc00002ca20}
StudentName: test2
ClassID: 1
StudentName: test4
ClassID: 1
StudentName: test4
ClassID: 1
StudentName: test3
ClassID: 2
StudentName: test3
ClassID: 2
StudentName: test5
ClassID: 3
StudentName: test5
ClassID: 3
Not
使用not查询,name!='test%'的,也就是包括test的都排除
// 查询
var students []entity.Student
find := db.Not("name like ?", "test%").Order("class_id").Find(&students)
//find := db.Where("name like ?", "test%").Order("class_id").Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
输出结果
Find> &{<nil> 0xc0000ae000}
StudentName: cqq
ClassID: 1
删除
tx := db.Where("name =?", "test2").Delete(&entity.Student{})
fmt.Println("row RowsAffected", tx.RowsAffected)
// 查询
var students []entity.Student
find := db.Where("name like ?", "test%").Order("class_id").Find(&students)
//find := db.Where("name like ?", "test%").Order("class_id").Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
这里我们使用带条件的方式进行删除
输出结果:
row RowsAffected 1
Find> &{<nil> 0xc0000a4240}
StudentName: test4
ClassID: 1
StudentName: test4
ClassID: 1
StudentName: test3
ClassID: 2
StudentName: test3
ClassID: 2
StudentName: test5
ClassID: 3
StudentName: test5
ClassID: 3
同样,我们去数据库查看数据
更新
保存 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。
我们以一个不存在的例子
s2 := &entity.Student{
Name: "test6",
ClassID: 1,
Gender: "male",
Age: 16,
}
tx := db.Save(&s2)
fmt.Println("row RowsAffected", tx.RowsAffected)
// 查询
var students []entity.Student
find := db.Where("name like ?", "test%").Order("class_id").Find(&students)
//find := db.Where("name like ?", "test%").Order("class_id").Find(&students)
if find.Error != nil {
fmt.Println("查询出错:", find.Error)
return
}
fmt.Println("Find>", find.Row())
for _, student := range students {
fmt.Println("StudentName:", student.Name)
fmt.Println("ClassID:", student.ClassID)
}
s2这个对象的数据在数据库中不存在,于是输出结果如下:
row RowsAffected 1
Find> &{<nil> 0xc0001f0990}
StudentName: test4
ClassID: 1
StudentName: test4
ClassID: 1
StudentName: test6
ClassID: 1
StudentName: test3
ClassID: 2
StudentName: test3
ClassID: 2
StudentName: test5
ClassID: 3
StudentName: test5
ClassID: 3
数据库查询
mysql> select * from students;
+----+-------+--------+-----+------------+----------+---------------------+---------------------+---------------------+
| id | name | gender | age | birthday | class_id | created_at | updated_at | deleted_at |
+----+-------+--------+-----+------------+----------+---------------------+---------------------+---------------------+
| 1 | cqq | male | 24 | 2000-05-05 | 1 | 2024-11-25 16:32:56 | 2024-11-25 16:32:56 | NULL |
| 4 | test2 | male | 24 | NULL | 1 | 2024-11-25 16:41:12 | 2024-11-25 16:41:12 | 2024-11-25 19:44:42 |
| 5 | test3 | male | 1 | NULL | 2 | 2024-11-25 16:44:39 | 2024-11-25 16:44:39 | NULL |
| 6 | test4 | male | 2 | NULL | 1 | 2024-11-25 16:44:39 | 2024-11-25 16:44:39 | NULL |
| 7 | test5 | male | 2 | NULL | 3 | 2024-11-25 16:44:39 | 2024-11-25 16:44:39 | NULL |
| 8 | test3 | male | 1 | NULL | 2 | 2024-11-25 16:47:33 | 2024-11-25 16:47:33 | NULL |
| 9 | test4 | male | 2 | NULL | 1 | 2024-11-25 16:47:33 | 2024-11-25 16:47:33 | NULL |
| 10 | test5 | male | 2 | NULL | 3 | 2024-11-25 16:47:33 | 2024-11-25 16:47:33 | NULL |
| 11 | test6 | male | 16 | | 1 | 2024-11-25 19:53:40 | 2024-11-25 19:53:40 | NULL |
+----+-------+--------+-----+------------+----------+---------------------+---------------------+---------------------+
9 rows in set (0.00 sec)