Gorm修炼之路---CRUD实战 | 青训营笔记

概述

GORM的特性

  • 全功能 ORM
  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
  • Create,Save,Update,Delete,Find 中钩子方法
  • 支持 PreloadJoins 的预加载
  • 事务,嵌套事务,Save Point,Rollback To Saved Point
  • Context、预编译模式、DryRun 模式
  • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
  • SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
  • 复合主键,索引,约束
  • Auto Migration
  • 自定义 Logger
  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus...
  • 每个特性都经过了测试的重重考验
  • 开发者友好

一、模型定义

概念

模型是标准的 struct,由 Go 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成。

例如:

go 复制代码
type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

约定

GORM 倾向于约定优于配置,默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。

gorm.Model

GORM 定义一个 gorm.Model 结构体,其包括字段 IDCreatedAtUpdatedAtDeletedAt,可以将它嵌入到自己定义的结构体中,以包含这几个字段。

go 复制代码
//gorm.Model 的定义
type Model struct{
    
  ID          uint            `gorm:"primaryKey"`
  CreatedAt   time.Time
  UpdatedAt   time.Time
  DeletedAt   gorm.DeletedAt  `gorm:"index"`

}

嵌入结构体

对于匿名字段,GORM 会将其字段包含在父结构体中,例如:

go 复制代码
type User struct {
    gorm.Model   
    UserName string
    Password string
} 
// 等效于 
type User struct {
    ID        uint           `gorm:"primaryKey"`   
    CreatedAt time.Time   
    UpdatedAt time.Time   
    DeletedAt gorm.DeletedAt `gorm:"index"`   
    UserName string 
    Password string
    }

二、连接到数据库

(本人使用的是Mysql,就不说明其他数据库的连接方法了)

Mysql

go 复制代码
import (   
    "gorm.io/driver/mysql"   
    "gorm.io/gorm" 
)

func main() {

    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db , err := gorm.Open(mysql.Open(dsn),&gorm.Config{})
    
}

三、CRUD操作

1. 创建记录

创建一条数据

使用db.Create方法,传入结构体的指针进行记录的创建。

sql 复制代码
user := User{UserName: "lps", Password: "123"} 
result := db.Create(&user)

返回的数据,user.ID(主键),result.Error(错误),result.RowsAffected(行数)

等同于以下sql语句。

go 复制代码
INSERT INTO 
`users `
(`created_at`,`updated_at`,`deleted_at`,`user_name`,`password`) 
VALUES 
('2023-08-02 17:19:00.250','2023-08-02 17:19:00.250',NULL,'lps','123')

gorm 会自动维护 created_at、updated_ad 和 deleted_at 三个字段。

只插入指定的字段

使用Select方法,选择指定字段插入。

sql 复制代码
user := User{UserName: "lps", Password: "123"}
result := db.Select("UserName").Create(&user)

等同于以下 SQL。

sql 复制代码
INSERT INTO `users` (`user_name`) VALUES ('lps')

需要注意:使用 select 时不会自动维护 created_at、updated_ad 和 deleted_at。

批量插入

当需要批量插入时,传入一个切片即可。

css 复制代码
users := []User{
    {UserName: "lps", Password: "123"},
    {UserName: "yhy", Password: "456"},
    {UserName: "hhh", Password: "789"},
}
db.Create(&users)

2. 删除记录

删除一条数据

使用db.Delete方法,删除单条数据,需要指定ID,否则会批量删除。

ini 复制代码
user.ID = 20 
db.Delete(&user)

等同于以下Sql语句

go 复制代码
UPDATE `users` SET `deleted_at` = '2023-08-02 17:22:32.389' 
WHERE `users`.`id` = 20 AND `users`.`deleted_at` IS NULL

设置删除条件

通常使用Where方法进行条件设置。

sql 复制代码
db.Where("user_name = ?","lps").Delete(&user)
// DELETE FROM users WHERE user_name = `lps`;

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(也可以使用字符串)

sql 复制代码
db.Delete(&User{}, 10)           
//DELETE FROM users WHERE id = 10; 

db.Delete(&User{}, "10")         
// DELETE FROM users WHERE id = 10;  

db.Delete(&users, []int{1,2,3})  
// DELETE FROM users WHERE id IN (1,2,3); 

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

sql 复制代码
db.Where("user_name LIKE ?", "%lps%").Delete(&User{}) 
// DELETE from users where email LIKE "%lps%";

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

sql 复制代码
var users = []User{{ID: 1}, {ID: 2}, {ID: 3}} 
db.Delete(&users) 
// DELETE FROM users WHERE id IN (1,2,3);  

db.Delete(&users, "name LIKE ?", "%lps%") 
// DELETE FROM users WHERE name LIKE "%lps%" AND id IN (1,2,3);  

软删除

如果你的模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!

当调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,而后的一般查询方法将无法查找到此条记录。

sql 复制代码
// 删除一个ID为1的user数据
db.Delete(&User{}, 1)
// Update users SET deleted_at = "2023-08-02 18:13" WHERE id = 1;

如果你并不想嵌套gorm.Model,你也可以像下方例子那样开启软删除特性:

go 复制代码
type User struct {
    ID      int   
    Deleted gorm.DeletedAt   
    Name    string 
}

查找被软删除的数据

你可以使用Unscoped来查询被软删除的数据

scss 复制代码
db.Unscoped().Where("id = 1").Find(&users)
//SELECT * FROM users WHERE id = 1;

永久删除

你可以使用Unscoped方法来永久删除数据

scss 复制代码
db.Unscoped().Delete(&users)
//DELETE FROM users WHERE id = 1;

3. 更新记录

更新所有字段

使用Save方法会更新所有字段,即使是零值也会更新

sql 复制代码
db.First(&user) 
user.UserName = "" 
db.Save(&user)
//UPDATE users SET created_at = '2023-08-02 17:12:08.548', updated_at = '2023-08-02 17:17:40.891', deleted_at = NULL, user_name = '', password = 'ddd' WHERE id = 1

更新单个列

使用ModelUpdate方法更新单列,可以使用结构体作为选取条件,仅选择 ID

sql 复制代码
user.ID = 2 
db.Model(&user).Update("user_name", "lps")
// UPDATE users SET user_name = lps ,updated_at = 2023-08-02 18:12:34 WHERE id = 2;

也可在Model中设置空结构体,使用Where方法自己选取条件

sql 复制代码
db.Model(&User{}).Where("user_name = ?","lps").Update("user_name","spl")
// UPDATE users SET user_name ='spl' ,updated_at = '2023-08-02 18:12:34' WHERE user_name =lps';

更新多个列

使用Updates方法进行更新多列。支持structmap更新。当更新条件是struct时,零值不会更新,如果确保某列必定更新,使用Select选择该列。

php 复制代码
// 使用struct更新,只会更新非零字段,下面这个例子中Password为零值,则不更新
db.Model(&user).Updates(User{ID: 1,UserName: "hello", Password: ""}) 
// UPDATE users SET user_name='hello', updated_at = '2023-08-02 21:34:10' WHERE id = 1;  

// 使用map更新,下面这个例子中Password为零值,依然更新
db.Model(&user).Updates(map[string]interface{}{"ID": 1,"UserName:"hello", "Password": ""}) 
// UPDATE users SET user_name='hello', password = '',updated_at = '2023-08-02 21:34:10' WHERE id = 1;

更新选定字段

如果要更新选定的字段或更新时忽略某些字段,可以使用Select(选定),Omit(忽略)

sql 复制代码
//user.ID = 1
//使用map
db.Model(&user).Select("user_name").Updates(map[string]interface{}{"user_name": "lps","password": "123"})
// UPDATE SET user_name = 'lps' WHERE id = 1;

db.Model(&user).Omit( "user_name" ).Updates( map [ string ] interface {}{ "user_name" : "lps" , "password" : "123"}) 
//UPDATE users SET password = '123', updated_at='2023-08-02 21:34:10' WHERE id = 1;

//使用struct类似

批量更新

如果我们没有指定具有主键值的记录Model,GORM将执行批量更新

sql 复制代码
// 使用 struct  
db.Model(User{}).Where( "role = ?" , "admin" ).Updates(User{UserName: "hello" , Password: "123" })  
// UPDATE users SET user_name='hello',password = '123' WHERE role = 'admin';  

// 使用map更新 
db.Table( "users" ).Where( "id IN ?" , [] int { 10 , 11 }).Updates( map [ string ] interface {}{ "user_name" : "hello" , "password" : "123" })  
// UPDATE users SET user_name='hello',password = '123' WHERE id IN (10, 11);

更新的记录数

使用result := db.Model().Updates()可以获取受更新影响的行数

arduino 复制代码
result.RowsAffected //返回更新的记录数
result.Error        //返回更新时的错误

4. 查询记录

查询单个对象

gorm 提供了 First、Take、Last 方法。它们都是通过 LIMIT 1 来实现的,分别是主键升序、不排序和主键降序。

sql 复制代码
user := User{}

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

如果没有查询到对象,会返回 ErrRecordNotFound 错误。

css 复制代码
result := db.First(&user)
errors.Is(result.Error, gorm.ErrRecordNotFound)
result.RowsAffected

根据主键查询

First/Take/Last 等函数中设置第二个参数,该参数被认作是 ID。可以选择 intstring 类型。

sql 复制代码
db.First(&user, 10)

db.First(&user, "10")

选择 string 类型的变量时,需要注意 SQL 注入问题。

查询多个对象(列表)

使用 Find 方法查询多个对象。

bash 复制代码
users := []User{}
result := db.Find(&users)

返回值会映射到 users 切片上。

依然可以通过访问返回值上的 ErrorRowsAffected 字段获取异常和影响的行号。

vbnet 复制代码
result.Error
result.RowsAffected

设置查询条件 Where

gorm 提供了万能的 Where 方法,可以实现 =、<>、IN、LIKE、AND、>、<、BETWEEN 等方法,使用 ? 来占位。

sql 复制代码
db.Where("name = ?", "l").First(&user)
// SELECT * FROM users WHERE user_name = 'l' ORDER BY id LIMIT 1;

// 获取全部匹配的记录
db.Where("name <> ?", "l").Find(&users)
// SELECT * FROM users WHERE user_name <> 'l';

// IN
db.Where("name IN ?", []string{"lps", "qqq"}).Find(&users)
// SELECT * FROM users WHERE user_name IN ('lps','qqq');

// LIKE
db.Where("name LIKE ?", "%l%").Find(&users)
// SELECT * FROM users WHERE user_name LIKE '%l%';

// AND
db.Where("name = ? AND age = ?", "lzq", "aaa").Find(&users)
// SELECT * FROM users WHERE user_name = 'lzq' AND password = aaa;

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2023-07-26 00:00:00' AND '2023-08-02 00:00:00';

Where 快速设置条件的方法

传递 Struct、Map 和 切片时,可以实现更简便的设置条件。

php 复制代码
db.Where(&User{UserName:"lps", Password:"123"}).Find(&user)
db.Where(map[string]interface{}{"user_name": "lps", "password": "123"}).Find(&user)

结构体和 Map 的效果几乎是相等的。

两者唯一的不同之处在于 struct 中的零值字段不会查询。比如 0、""、false

切片是查询主键。

sql 复制代码
db.Where([]int{10, 11}).Find(&user)
// SELECT FROM users WHERE users.id IN (10,11) AND users.deleted_at IS NULL;

所有的查询,gorm 都会默认设置 tabel.deleted_at IS NULL 查询条件。

除了 Where 方法外,还有内联查询的方式,但是不推荐同时使用两种风格。

sql 复制代码
db.Find(&user, "user_name = ?", "lps")
// SELECT * FROM users WHERE user_name = "lps";

其他查询 Not & Or

gorm 还提供了 Not 和 Or 方法,但不推荐使用,因为 Where 同样可以实现两者的功能

sql 复制代码
db.Where("password = ?", "123").Not("user_name", "l").Or("id > ?", 10).Find(&users)
// SELECT * FROM users WHERE (( password = '123') AND user_name <> 'l' OR id > 10 AND users.deleted_at IS NULL;

选取特定字段 Select

使用 Select 方法。

sql 复制代码
db.Select("password").Where(&User{UserName:"lps"}).Find(&user)
// SELECT password FROM users WHERE users.user_name = 'lps' AND users.deleted_at IS NULL;

其他操作

排序 Order

vbnet 复制代码
db.Order("user_name desc, password").Find(&users)
// SELECT * FROM users WHERE users.deleted_at IS NULL ORDER BY user_name DESC,password;

分页 Limit Offset

LimitOffset 可以单独使用,也可以组合使用。

scss 复制代码
db.Limit(3).Find(&users)
db.Offset(3).Find(&users)

db.Limit(2).Offset(3).Find(&users)

分组 Group Having

根据 username 统计用户名的重复。

sql 复制代码
result := []map[string]interface{}{}
db.Model(&User{}).Select("user_name, SUM( id ) AS nums").Group("user_name").Find(&result)
// SELECT user_name,SUM( id ) AS nums FROM users GROUP BY user_name;

去重 Distinct

sql 复制代码
result := []string{}
db.Model(&User{}).Distinct("user_name").Find(&result)
// SELECT DISTINCT user_name FROM users

四、结束语

历时两天完成这篇文章,望支持点赞收藏,内容有任何错误欢迎提出,本人一定及时改正。本文只叙述了几个简单的增删改查功能,关于GORM的更多内容后续更新!

感谢您的观看!!!!

相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记