Gorm的CURD简单使用

125 阅读9分钟

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)