Gorm 入门指南
本文内容基于官方文档整理,并结合了个人实践经验与理解。考虑到官方网站访问速度较慢,我将核心内容提炼为笔记形式,既方便自己日后查阅,也希望能为其他开发者提供一份更易获取的参考资料。Gorm 中文文档
安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
快速入门
官方示例中是 sqlite ,我改写了一份 mysql 版本。
package main
import (
_ "github.com/go-sql-driver/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func getDsn() string {
return "root:root@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
}
func main() {
mysqlConfig := mysql.Config{
DSN: getDsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 迁移 schema
db.AutoMigrate(&Product{})
// Create
db.Create(&Product{Code: "D42", Price: 100})
// Read
var product Product
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
// Delete - 删除 product
db.Delete(&product, 1)
}
创建记录
Gorm 中文文档 - 创建记录创建单条记录
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
func getDsn() string {
return "root:root@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
}
func getDB() (*gorm.DB, error) {
mysqlConfig := mysql.Config{
DSN: getDsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
return gorm.Open(mysql.New(mysqlConfig), &gorm.Config{})
}
type User struct {
gorm.Model
Name string
Age int
Birthday time.Time
}
func main() {
db, err := getDB()
if err != nil {
panic("failed to connect database")
}
p := fmt.Println
// 迁移 schema
db.AutoMigrate(&User{})
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
p("返回插入数据的主键", user.ID)
p("返回 error", result.Error)
p("返回插入记录的条数", result.RowsAffected)
}
创建多条记录
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
func getDsn() string {
return "root:root@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
}
func getDB() (*gorm.DB, error) {
mysqlConfig := mysql.Config{
DSN: getDsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
return gorm.Open(mysql.New(mysqlConfig), &gorm.Config{})
}
type User struct {
gorm.Model
Name string
Age int
Birthday time.Time
}
func main() {
db, err := getDB()
if err != nil {
panic("failed to connect database")
}
p := fmt.Printf
p2 := fmt.Println
// 迁移 schema
db.AutoMigrate(&User{})
users := []*User{
{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
{Name: "Jackson", Age: 19, Birthday: time.Now()},
}
result := db.Create(&users) // 通过数据的指针来创建
p("users 结构体内容", users)
p2()
p2(users[0].ID)
p("result 结构体内容", result)
p2()
p2("返回 error", result.Error)
p2("返回插入记录的条数", result.RowsAffected)
}
使用 Map 来创建记录
package main
import (
_ "github.com/go-sql-driver/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
func getDsn() string {
return "root:root@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
}
func getDB() (*gorm.DB, error) {
mysqlConfig := mysql.Config{
DSN: getDsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
return gorm.Open(mysql.New(mysqlConfig), &gorm.Config{})
}
type User struct {
gorm.Model
Name string
Age int
Birthday time.Time
}
func main() {
db, err := getDB()
if err != nil {
panic("failed to connect database")
}
// 迁移 schema
db.AutoMigrate(&User{})
db.Model(&User{}).Create(map[string]interface{}{
"Name": "jinzhu", "Age": 18, "Created_at": time.Now(),
})
// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})
}
当使用 map 来创建时,钩子方法不会执行,关联不会被保存且不会回写主键。
最明显的变化就是表记录不会自动添加创建时间和更新时间了。
查询记录
Gorm 中文文档 - 查询记录检索单个对象
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)将会查询整个表并且只返回第一个对象,只是性能不高并且不确定的。
First
and Last
方法会按主键排序找到第一条记录和最后一条记录 (分别)。 只有在目标 struct 是指针或者通过 db.Model()
指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:
type User struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
Name string
}
var user User
var users []User
// 有效,因为传入了目标结构
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 之所以有效,是因为模型是使用`db.Model(&User{})`指定的
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// doesn't work
result := map[string]interface{}{}
db.Table("users").First(&result)
// works with Take
result := map[string]interface{}{}
db.Table("users").Take(&result)
// SELECT * FROM `users` LIMIT 1
// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct {
Code string
Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
根据主键检索
如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免 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);
如果主键是字符串( 例如 uuid ),查询将被写成如下:
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;
如果使用 gorm 的特定字段类型(例如 gorm.DeletedAt),它将运行不同的查询来检索对象。
type User struct {
ID string `gorm:"primarykey;size:16"`
Name string `gorm:"size:24"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
var user = User{ID: 15}
db.First(&user)
// SELECT * FROM `users` WHERE `users`.`id` = '15'
// AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
检索全部对象
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // returns found records count, equals `len(users)`
result.Error // returns error
条件查询
注意不带 DeletedAt 字段
String 条件
type User struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
Name string
}
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at
// BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
如果对象设置了主键,条件查询将不会覆盖主键的值,而是用 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
这个查询将会给出 record not found 错误 所以,在你想要使用例如 user 这样的变量从数据库中获取新值前,需要将例如 id 这样的主键设置为 nil。
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进行查询时,GORM只会使用非零字段进行查询,这意味着如果字段的值为0、’’、false或其他零值,则不会用于构建查询条件,例如:
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
要在查询条件中包含零值,可以使用映射,该映射将包含所有键值作为查询条件,例如:
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
指定结构体查询字段
使用 struct 搜索时,您可以通过将相关字段名或 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;
内联条件
查询条件可以以类似于 Where 的方式内联到 First 和 Find 等方法中。
// 如果主键是非整数类型,则按主键获取
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';
// 纯 SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;
db.Find(&users, User{Age: 20, Name: ""})
// SELECT * FROM users WHERE age = 20;
// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;
db.Find(&user, map[string]interface{}{"age": 20, "name": ""})
// SELECT * FROM users WHERE age = 20 AND name = "";
Not 条件
构建非条件,工作类似于 Where
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");
// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
db.Debug().Not(User{Name: "jinzhu", Age: 0}).First(&user)
// SELECT * FROM users WHERE name <> 'jinzhu' ORDER BY id LIMIT 1
db.Debug().Not(map[string]interface{}{"name": "jinzhu", "age": 0}).First(&user)
// SELECT * FROM users WHERE (age <> 0 AND name <> 'jinzhu') ORDER BY id LIMIT 1
// 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 条件
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// Map
db.Where("name = 'jinzhu'")
.Or(map[string]interface{}{"name": "jinzhu 2", "age": 18})
.Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
选择特定字段
选择允许您指定要从数据库中检索的字段。否则,GORM 将默认选择所有字段。
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
排序
从数据库检索记录时指定顺序
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
Limit & Offset
Limit 指定要检索的最大记录数 Offset 指定在开始返回记录之前要跳过的记录数。
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
分页
func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB {
return func (db *gorm.DB) *gorm.DB {
q := r.URL.Query()
page, _ := strconv.Atoi(q.Get("page"))
if page <= 0 {
page = 1
}
pageSize, _ := strconv.Atoi(q.Get("page_size"))
switch {
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
db.Scopes(Paginate(r)).Find(&users)
db.Scopes(Paginate(r)).Find(&articles)
Group By & Having
type result struct {
Date time.Time
Total int
}
db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
defer rows.Close()
for rows.Next() {
...
}
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
defer rows.Close()
for rows.Next() {
...
}
type Result struct {
Date time.Time
Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
Distinct
指定查询结果中只返回不重复的字段组合。
db.Distinct("name", "age").Order("name, age desc").Find(&user)
// SELECT DISTINCT `name`,`age` FROM `users` ORDER BY name, age desc
Joins
type result struct {
Name string
Email string
}
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
...
}
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
// // SELECT `users`.`id`,`users`.`created_at`,`users`.`updated_at`,`users`.`name`,`users`.`age` FROM `users` JOIN emails ON emails.user_id = users.id AND emails.email = 'jinzhu@example.org' JOIN credit_cards ON credit_cards.user_id = users.id WHERE credit_cards.number = '411111111111'
Joins 预加载
可以将 Joins 与单个 SQL 结合使用,例如:
type User struct {
ID uint
Name string
CompanyID uint // 外键
Company Company // 关联的Company模型
}
type Company struct {
ID uint
Name string
}
db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`company_id`,
// `Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name`
// FROM `users` LEFT JOIN `companies` `Company`
// ON `users`.`company_id` = `Company`.`id`
// inner join
db.InnerJoins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`company_id`,
// `Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name`
// FROM `users` INNER JOIN `companies` `Company`
// ON `users`.`company_id` = `Company`.`id`
带有条件的 Joins
db.Joins("Company", db.Where(&Company{Name: "h"})).Find(&user)
// SELECT `users`.`id`,`users`.`name`,`users`.`company_id`,`Company`.`id` AS `Company__id`,
// `Company`.`name` AS `Company__name` FROM `users`
// LEFT JOIN `companies` `Company`
// ON `users`.`company_id` = `Company`.`id` AND `Company`.`name` = 'h'
更新的细节,请参考 预加载
Joins 一个衍生表
还可以使用联接来联接派生表。
type User struct {
Id int
Age int
}
type Order struct {
UserId int
FinishedAt *time.Time
}
query := db.Table("order").Select("MAX(order.finished_at) as latest").Joins("left join user user on order.user_id = user.id").Where("user.age > ?", 18).Group("order.user_id")
db.Model(&Order{}).Joins("join (?) q on order.finished_at = q.latest", query).Scan(&results)
// SELECT `order`.`user_id`,`order`.`finished_at` FROM `order` join (SELECT MAX(order.finished_at) as latest FROM `order` left join user user on order.user_id = user.id WHERE user.age > 18 GROUP BY `order`.`user_id`) q on order.finished_at = q.latest
Scan
将结果扫描到结构体中的工作方式与我们使用 Find 的方式类似。
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// Raw SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
更新记录
Gorm 中文文档 - 更新记录保存所有字段
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 错误,查看阻止全局更新了解详情。
当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:
// 根据条件更新
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;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10'
// WHERE id=111 AND active=true;
更新多列
Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下 GORM 只会更新非零值的字段。
// User 的 ID 是 `1`:
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 0})
// UPDATE `users` SET `age`=0,`name`='hello' WHERE `id` = 1
db.Model(&user).Select("name", "age").Updates(User{Name: "hello", Age: 0})
// UPDATE `users` SET `name`='hello',`age`=0 WHERE `id` = 1
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 0})
// UPDATE `users` SET `name`='hello' WHERE `id` = 1
使用 struct 更新时, GORM 将只更新非零值字段。 你可能想用 map 来更新属性,或者使用 Select 声明字段来更新。
更新选定字段
在更新时选择、忽略某些字段,可以使用 Select、Omit 方法。
// 选择 Map 的字段
// User 的 ID 是 `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;
// 选择 Struct 的字段(会选中零值的字段)
db.Model(&user).Select("Name", "Age")
.Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// 选择所有字段(选择包括零值字段的所有字段)
db.Model(&user).Select("*")
.Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
// 选择除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role")
.Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
更新 Hook
GORM 支持的 hook 包括:BeforeSave, BeforeUpdate, AfterSave, AfterUpdate。 更新记录时将调用这些方法,查看 Hooks 获取详细信息。
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to update")
}
return
}
批量更新
如果没有通过 Model 指定一个含有主键的记录,GORM 会执行批量更新
// Update with struct
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
// Update with map
db.Table("users").Where("id IN ?", []int{10, 11})
.Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
阻止全局更新
如果你执行一个没有任何条件的批量更新,GORM 默认不会运行,并且会返回 ErrMissingWhereClause 错误
你可以用一些条件,使用原生 SQL 或者启用 AllowGlobalUpdate 模式,例如:
db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause
db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu" WHERE 1=1
db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"
更新的记录数
获取受更新影响的行数
// Get updated records count with `RowsAffected`
result := db.Model(User{}).Where("role = ?", "admin")
.Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
result.RowsAffected // returns updated records count
result.Error // returns updating error
删除记录
Gorm 中文文档 - 删除记录删除一条记录
删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:
// 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"
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;
删除标志
默认情况下,gorm.Model 使用 *time.Time 作为 DeletedAt 的字段类型。
不过软删除插件 gorm.io/plugin/soft_delete 同时也提供其他的数据格式支持。
当使用 DeletedAt 创建唯一复合索引时,你必须使用其他的数据类型。
例如通过 gorm.io/plugin/soft_delete 插件将字段类型定义为 unix 时间戳等等。
import "gorm.io/plugin/soft_delete"
type User struct {
ID uint
Name string `gorm:"uniqueIndex:udx_name"`
DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"`
}
Unix 时间戳
使用 unix 时间戳作为删除标志。
import "gorm.io/plugin/soft_delete"
type User struct {
ID uint
Name string
DeletedAt soft_delete.DeletedAt
}
// 查询
SELECT * FROM users WHERE deleted_at = 0;
// 软删除
UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
你同样可以指定使用毫秒 milli 或纳秒 nano 作为值,如下例:
type User struct {
ID uint
Name string
DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"`
// DeletedAt soft_delete.DeletedAt `gorm:"softDelete:nano"`
}
// 查询
SELECT * FROM users WHERE deleted_at = 0;
// 软删除
UPDATE users SET deleted_at = /* current unix milli second or nano second */ WHERE ID = 1;
使用 1 / 0 作为 删除标志
import "gorm.io/plugin/soft_delete"
type User struct {
ID uint
Name string
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}
// 查询
SELECT * FROM users WHERE is_del = 0;
// 软删除
UPDATE users SET is_del = 1 WHERE ID = 1;
混合模式
混合模式可以使用 0,1 或者 unix 时间戳来标记数据是否被软删除,并同时可以保存被删除时间。
type User struct {
ID uint
Name string
DeletedAt time.Time
// use `1` `0`
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"`
// use `unix second`
// IsDel soft_delete.DeletedAt `gorm:"softDelete:,DeletedAtField:DeletedAt"`
// use `unix nano second`
// IsDel soft_delete.DeletedAt `gorm:"softDelete:nano,DeletedAtField:DeletedAt"`
}
// 查询
SELECT * FROM users WHERE is_del = 0;
// 软删除
UPDATE users SET is_del = 1, deleted_at = /* current unix second */ WHERE ID = 1;