正文之前也反应大家去我的博客看看喔笔者的blog
Gorm的基本使用
在文章之前,笔者想说,gorm相关内容在gorm的官方中文文档,写得很详细,也推荐大家学习观看喔~ 下面是读者自己觉得学习gorm过程中,觉得很重要的内容,反应大家指导观看。
下载相关包
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
我i这里是使用mysql来作为数据库,所以在在 go get -u gorm.io/driver/mysql
后面写的是 mysql
。下载你想使用的数据库包
简单示例
连接数据库:
dsn := "root:wst113929@tcp(127.0.0.1:3306)/database_learn?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
注意:一般操作均为 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
,其中如果是mysql的情况均为 :
dsn := "username:password@tcp(hostname:port)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
然后进行相关数据库操作,总体代码如下
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Person struct {
gorm.Model
Name string
Age int
}
func main() {
dsn := "root:wst113929@tcp(127.0.0.1:3306)/database_learn?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//迁移schema
db.AutoMigrate(&Person{})
//创建
db.Create(&Person{Age: 10, Name: "hhh"})
db.Create(&Person{Age: 19, Name: "wst"})
//读取
var p1 Person
db.First(&p1, 1) //根据主键id
fmt.Printf("%#v\n", p1)
var p2 Person
db.First(&p2, "name=?", "wst") //寻找匹配字段name为wst的第一条数据
fmt.Printf("%#v\n", p2)
//更新
db.Model(&p2).Update("name", "aaa") //更新一个字段
db.Model(&p2).Updates(Person{
Age: 100,
}) //更新多个字段
//删除指定的记录
db.Delete(&p1)
//删除指定的记录,其中&Person{}是要删除的类型
db.Where("name=?", "wst").Delete(&Person{})
//删除所有记录
db.Delete(Person{}) //谨慎操作,这是传递类型,删除所有
}
ps:此时的操作只是一次性的,并没有在数据库中设置相关操作来保证数据的唯一性,如果你多次运行该代码,或者写部分代码运行一次等不同操作,会造成数据库数据重复。就像下图中,数据的紊乱 。之后我们再来修改该问题。
gorm介绍
gorm.Model 介绍
gorm.Model
是 GORM 提供的一个结构体,用于定义模型的基本字段和属性。
//结构定义如下:
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
gorm.Model
提供了一些基本的字段和属性,用于支持常见的数据库操作和模型生命周期的管理。通过嵌入 gorm.Model
,你可以轻松地在你的模型中继承这些字段和属性。
例如,我上文定义的一个模型 Person
并嵌入 gorm.Model
,Person
将自动包含 ID
、CreatedAt
、UpdatedAt
和 DeletedAt
字段。
//直接可以定义的结构
type Person struct {
gorm.Model
Name string
Age int
}
//就是等价于
type Person struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Name string
Age int
}
这样,Person
模型将具有 ID
、CreatedAt
、UpdatedAt
和 DeletedAt
字段,并且可以通过 GORM 进行数据库操作,包括创建、读取、更新和软删除等操作。
声明模型
GORM 倾向于约定优于配置 默认情况下,GORM 使用
ID
作为主键,使用结构体名的蛇形复数
作为表名,字段名的蛇形
作为列名,并使用CreatedAt
、UpdatedAt
字段追踪创建、更新时间。如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,Gorm允许自己配置。
就像gorm.Model
中字段id
一样,你会发现在id
有标签 gorm:"primary_key"
,这就是gorm给大家提供的标签。
对于正常的结构体字段,我们可以通过标签 embedded
将其嵌入,例如:
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
并且,也可以使用标签 embeddedPrefix
来为 db 中的字段名添加前缀,例如:
type Blog struct {
ID int
Author Author `gorm:"embedded;embeddedPrefix:author_"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
AuthorName string
AuthorEmail string
Upvotes int32
}
下面列举一下常用的标签使用
标签 | 说明 |
---|---|
column | 指定数据库表中对应字段的名称 |
type | 指定数据库字段的数据类型 |
size | 指定数据库字段的长度或大小 |
primaryKey | 标记字段为主键 |
autoIncrement | 标记字段为自增主键 |
unique | 标记字段值必须在整个表中是唯一的 |
index | 在数据库中为字段创建索引 |
not null | 标记字段不能为空值 |
default | 指定字段的默认值 |
comment | 注释字段,用于记录字段的说明或备注 |
embedded | 嵌入其他模型或结构体,将其字段合并到当前模型中 |
ignore | 忽略该字段,GORM 将不会处理该字段 |
association_foreignkey | 指定关联关系中外键字段的名称 |
以上是一些常见的 GORM 标签,我们根据需要在模型的字段上使用这些标签来定义数据库表的结构和约束。根据需要来选择标签。其他标签可参见官方文档哦~Gorm官方文档/标签
主键、表名、列名的约定
-
主键
- 如果模型的字段名是
ID
(不区分大小写),它将被视为默认的主键字段。 - 如果你的模型没有名为
ID
的字段,你可以通过在字段上使用gorm:"primaryKey"
标签来将其标记为主键。
- 如果模型的字段名是
-
表名
- 默认情况下,GORM 使用模型的结构体名称的复数形式作为数据库表的名称。例如,
type User struct{}
的数据库表名将是users
。 - 你可以通过在模型的结构体上使用
gorm:"tableName:custom_table_name"
标签来自定义表名
- 默认情况下,GORM 使用模型的结构体名称的复数形式作为数据库表的名称。例如,
-
列名
- 默认情况下,GORM 使用模型的字段名作为数据库表中对应列的名称。
- 你可以通过在字段上使用
gorm:"column:custom_column_name"
标签来自定义列名。
如以下例子:
type Person struct {
gorm.Model
Name string `gorm:"column:name"` // 自定义列名
Age int `gorm:"column:age"` // 自定义列名
}
func (Person) TableName() string {
return "my_custom_table_name" // 自定义表名
}
通过在结构体字段上使用 gorm:"column:name"
和 gorm:"column:age"
来自定义列名,以及通过 TableName()
方法来自定义表名。
在上述示例中,Person
结构体的表名被自定义为 "my_custom_table_name"
。
注意:在自定义表名时,你需要确保表名在数据库中是唯一的,并且与其他表没有冲突。
CURD操作
首先结构是:
type User struct {
ID uint
Name string
Age uint8
Birthday time.Time
}
增加 insert
创建记录
创建单条记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
也可以直接创建多项记录
db.Create([]User{
User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
User{Name: "Jackson", Age: 19, Birthday: time.Now()}
})
需要注意的是,db.Create()
方法是一个原子操作,它会在单个数据库事务中执行所有的插入操作,确保数据一致性。如果其中一个插入操作失败,所有的插入操作都将被回滚,数据不会被插入到数据库。
用指定的字段创建记录
创建记录并为指定的字段分配值:
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
创建记录并忽略要省略的传递字段的值:
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
批量插入
用slice和map的跟上方创建按多项记录相差不大。
查询 select
检索单个对象
GORM 提供了 First
、Take
、Last
方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1
条件,且没有找到记录时,它会返回 ErrRecordNotFound
错误
// 获取第一条记录(主键升序)
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;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
如果你想避免
ErrRecordNotFound
错误,你可以使用Find
,比如db.Limit(1).Find(&user)
,Find
方法可以接受struct和slice的数据。
对单个对象使用
Find
而不带limit,db.Find(&user)
将会查询整个表并且只返回第一个对象,这是性能不高并且不确定的。
此外,如果没有为相关模型定义主键,则模型将按第一个字段排序。
根据主键检索
如果主键是数字,则可以使用内联条件使用主键检索对象。
使用字符串时,需要格外小心以避免 SQL 注入;有关详细信息,请查看“安全性”部分。
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
如果是主键是string的话,使用下列方法
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
当目标对象有一个主键值时,将使用主键构建查询条件,例如:
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;
var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;
检索全部对象
使用 find()
来查询所有
result:=db.Find(&user)
// SELECT * FROM users;
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
条件查询
-
string查询
// Get first matched record db.Where("name = ?", "jinzhu").First(&user) // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1; // AND db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users) // SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22; // LIKE db.Where("name LIKE ?", "%jin%").Find(&users) // SELECT * FROM users WHERE name LIKE '%jin%'; // IN db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users) // SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
在还有
<>
或者betwwen
等操作,详情请参考gorm官方文档查询如果已设置对象的主键,则条件查询不会涵盖主键的值,而是将其用作“and”条件。
var user = User{ID: 10} db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1
-
Struct & Map 条件
// Struct db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1; // Map db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20; // Slice of primary keys db.Where([]int64{20, 21, 22}).Find(&users) // SELECT * FROM users WHERE id IN (20, 21, 22);
但是注意:如果用struct查询,会忽略零值,如
0
,''
,false
,不会形成查询语句db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)// SELECT * FROM users WHERE name = "jinzhu";
除非使用map,可以查询零值:
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
使用结构进行搜索时,可以通过将相关字段名称或 dbname 传递给 Where()来指定要在查询条件中使用的结构中的哪些特定值,例如:
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 0; db.Where(&User{Name: "jinzhu"}, "Age").Find(&users) // SELECT * FROM users WHERE age = 0;
内联条件、Not条件
这两个用法跟where
相似,直接使用,下面显示相关用法
//--------内联
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
//---------Not
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
Or条件
就是在条件中添加or
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
其他查询语句
select、Limit & Offset、Group By & Having、order、Distinct等相关语句,其实差不多。
select
是查询特定字段。
Limit & Offset
是进行偏移和限制数量
Group By & Having
查询分组后的结果
order
进行排序
Distinct
对相同数据的筛选
以上函数可以随便搜索找一下用法就可以使用,详细内容请看官方内容|查询特定字段
Scan函数、count函数
scan
用法相同于find函数,都是最后赋值给scan
和find
内部的变量
count
函数获取数量,注意:该函数应该在最后,否则会覆盖前面的内容
//数量count,应该在最后
var count int64
db.Model(&User{}).Where("name=?", "Jinzhu").Or("name=?", "Jackson").Count(&count)
println(count)
还有 pluck
函数经常用到:
//pluck :查询model中的一个列
var ages []int64
db.Model(&User{}).Pluck("age", &ages)
for index, _ := range ages {
println(ages[index])
}
更新 update
保存所有字段
Save
会保存所有的字段,即使字段是零值
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
保存是一个组合功能。如果保存值不包含主键,它将执行 Create
,否则将执行 Update
(包含所有字段)。
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1
注意:不要再在save使用model,否则会触发未定义行为
更新单个列和多个列
使用 Update 更新单个列时,它需要具有任何条件,否则将引发错误ErrMissingWhereClause
// Update with conditions
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
多个列的情况下使用 updates
函数,使用struct
或者map
对象
// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111; 这里id是因为在user这里面有id的设置
// Update attributes with `map`
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
注意:使用 Updates
方法可以更新多个列,并且只会更新非零值字段。且对于未提供值的字段,它们将保持原有的值不变。
更新所选的字段
使用Select
, Omit
来更新
// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// Select with Struct (select zero value fields)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// Select all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
其他更新内容
在官方中,还提供更新hook
、子查询更新等高级操作,有想法可以看官方文档哦~
链接奉上:更新 | 高级操作.
删除delete
删除一条记录
删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";
根据主键删除
GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字。
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 支持 BeforeDelete
、AfterDelete
Hook,在删除记录时会调用这些方法,可以查看 Hook 获取详情
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}
批量删除
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
可以将一个主键切片传递给Delete
方法,以便更高效的删除数据量大的记录
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 ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3);
阻止全局删除
当你试图执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause
错误
如果一定要这么做,你必须添加一些条件,或者使用原生SQL,或者开启AllowGlobalUpdate
模式,如下例:
db.Delete(&User{}).Error // gorm.ErrMissingWhereClause
db.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error // gorm.ErrMissingWhereClause
db.Where("1 = 1").Delete(&User{})
// DELETE FROM `users` WHERE 1=1
db.Exec("DELETE FROM users")
// DELETE FROM users
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users
返回删除行的数据
返回被删除的数据,仅当数据库支持回写功能时才能正常运行,如下例:
// 回写所有的列
var users []User
DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING *
// users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
// 回写指定的列
DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`
// users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}
软删除
如果你的模型包含了 gorm.DeletedAt
字段(该字段也被包含在gorm.Model
中),那么该模型将会自动获得软删除的能力!
当调用Delete
时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt
设置为当前时间,而后的一般查询方法将无法查找到此条记录。
// user's ID is `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// Batch Delete
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// Soft deleted records will be ignored when querying
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
如果你并不想嵌套gorm.Model
,你也可以像下方例子那样开启软删除特性:
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
查找被软删除的记录
你可以使用Unscoped
来查询到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
永久删除
你可以使用 Unscoped
来永久删除匹配的记录
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// Batch Delete
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// Soft deleted records will be ignored when querying
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
如果你并不想嵌套gorm.Model
,你也可以像下方例子那样开启软删除特性:
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
查找被软删除的记录
你可以使用Unscoped
来查询到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
永久删除
你可以使用 Unscoped
来永久删除匹配的记录
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;