概述
GORM的特性
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持
Preload
、Joins
的预加载 - 事务,嵌套事务,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 的基本数据类型、实现了 Scanner
和 Valuer
接口的自定义类型及其指针或别名组成。
例如:
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
作为主键,使用结构体名的 蛇形复数
作为表名,字段名的 蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
字段追踪创建、更新时间。
gorm.Model
GORM 定义一个 gorm.Model
结构体,其包括字段 ID
、CreatedAt
、UpdatedAt
、DeletedAt
,可以将它嵌入到自己定义的结构体中,以包含这几个字段。
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
更新单个列
使用Model
和Update
方法更新单列,可以使用结构体作为选取条件,仅选择 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
方法进行更新多列。支持struct
和map
更新。当更新条件是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
。可以选择 int
或 string
类型。
sql
db.First(&user, 10)
db.First(&user, "10")
选择 string
类型的变量时,需要注意 SQL 注入问题。
查询多个对象(列表)
使用 Find
方法查询多个对象。
bash
users := []User{}
result := db.Find(&users)
返回值会映射到 users
切片上。
依然可以通过访问返回值上的 Error
和 RowsAffected
字段获取异常和影响的行号。
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
Limit
和 Offset
可以单独使用,也可以组合使用。
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的更多内容后续更新!