Gorm学习笔记 - CRUD记要
教程地址:https://www.kancloud.cn/sliver_horn/gorm/1861154
上一篇:https://blog.csdn.net/qq_17523181/article/details/152366210?spm=1011.2415.3001.5331
一、模型定义
1. 字段级权限控制
type User struct {Name string `gorm:"<-:create"` // 允许读和创建Name string `gorm:"<-:update"` // 允许读和更新Name string `gorm:"<-"` // 允许读和写(创建和更新)Name string `gorm:"<-:false"` // 允许读,禁止写Name string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写)Name string `gorm:"->;<-:create"` // 允许读和写Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)Name string `gorm:"-"` // 读写操作均会忽略该字段
}
2. 创建/更新时间追踪(纳秒、毫秒、秒、Time)
type User struct {CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充UpdatedAt int // 在创建时该字段值为零值或者在更新时,使用当前秒级时间戳填充Updated int64 `gorm:"autoUpdateTime:nano"` // 使用纳秒级时间戳填充更新时间Updated int64 `gorm:"autoUpdateTime:milli"` // 使用毫秒级时间戳填充更新时间Created int64 `gorm:"autoCreateTime"` // 使用秒级时间戳填充创建时间
}
3. 通过标签 embedded 将其嵌入,例如使用embedded后,可以Blog.Name,本来要使用Blog.Author.Name
type Author struct {Name stringEmail string
}type Blog struct {ID intAuthor Author `gorm:"embedded"`Upvotes int32
}
// 等效于
type Blog struct {ID int64Name stringEmail stringUpvotes int32
}
4. 可以使用标签 embeddrefix 来为 db 中的字段名添加前缀
type Blog struct {ID intAuthor Author `gorm:"embedded;embeddedPrefix:author_"`Upvotes int32
}
// 等效于
type Blog struct {ID int64AuthorName stringAuthorEmail stringUpvotes int32
}
5. 标签名说明
标签名 | 说明 |
---|---|
column | 指定 db 列名 |
type | 列数据类型,推荐使用兼容性好的通用类型,例如:bool、int、uint、float、string、time、bytes。 并可与其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。 在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSINED not NULL AUTO_INSTREMENT |
size | 指定列大小,例如:size:256 |
unique | 指定列为唯一 |
primaryKey | 指定列为主键 |
default | 指定列的默认值 |
precision | 指定列的精度 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌套字段的前缀 |
autoCreateTime | 创建时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳, 例如:autoCreateTime:nano |
autoUpdateTime | 创建/更新时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳, 例如:autoUpdateTime:milli |
index | 根据参数创建索引,多个字段拥有相同的名称则创建复合索引,参考 索引 获取详情 |
uniqueIndex | 与 index 相同,但创建的是唯一索引 |
check | 创建检查约束,例如 check:(age > 13),查看 约束 获取详情 |
<- | 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无权限 |
-> | 设置字段读取权限 |
- | 忽略此字段(禁止读写) |
二、创建
1. 创建钩子
GORM 允许 BeforeSave, BeforeCreate, AfterSave, AfterCreate 等钩子,创建记录时会调用这些方法
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {u.UUID = uuid.New()if u.Role == "admin" {return errors.New("invalid role")}return
}
2. 批量插入
将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
DB.Create(&users)for _, user := range users {user.ID // 1,2,3
}
3. 关联创建与跳过关联
如果您的模型定义了任何关系(relation),并且它有非零关系,那么在创建时这些数据也会被保存
type CreditCard struct {gorm.ModelNumber stringUserID uint
}type User struct {gorm.ModelName stringCreditCard CreditCard
}db.Create(&User{Name: "jinzhu",CreditCard: CreditCard{Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...
您也可以通过 Omit 跳过关联保存
db.Omit("CreditCard").Create(&user)// 跳过所有关联
db.Omit(clause.Associations).Create(&user)
三、查询
1. GORM 提供 First, Take, Last 方法,以便从数据库中检索单个对象。
2. Distinct
从模型中选择不相同的值
db.Distinct("name", "age").Order("name, age desc").Find(&results)
3. Locking (FOR UPDATE)
GORM 支持多种类型的锁,
作用:
排他锁(FOR UPDATE)是悲观锁的一种实现,它会锁定查询到的所有行,防止其他事务对这些行进行更新或删除操作(其他事务仍可读取数据)。锁会在当前事务提交或回滚时自动释放。
应用场景:
主要用于需要强一致性的并发操作,例如电商系统中的库存扣减、资金转账等场景。例如,在用户下单时,需要先锁定商品库存记录,防止其他用户同时下单导致超卖
例如:
DB.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATEDB.Clauses(clause.Locking{Strength: "SHARE",Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`
4. 一些高级查询
- FirstOrInit
获取第一条匹配的记录,或者根据给定的条件初始化一个 struct(仅支持 sturct 和 map 条件) - FirstOrCreate
获取第一条匹配的记录,或者根据给定的条件创建一条新纪录(仅支持 sturct 和 map 条件) - 查询钩子
对于查询操作,GORM 支持 AfterFind 钩子,查询记录后会调用它
func (u *User) AfterFind(tx *gorm.DB) (err error) {if u.Role == "" {u.Role = "user"}return
}
5. Pluck
Pluck 用于从数据库查询单个列,并将结果扫描到切片。如果您想要查询多列,您应该使用 Scan
6. Scopes
Scopes 允许你指定常用的查询,可以在调用方法时引用这些查询
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {return db.Where("amount > ?", 1000)
}func PaidWithCreditCard(db *gorm.DB) *gorm.DB {return db.Where("pay_mode_sign = ?", "C")
}func PaidWithCod(db *gorm.DB) *gorm.DB {return db.Where("pay_mode_sign = ?", "C")
}func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {return func (db *gorm.DB) *gorm.DB {return db.Where("status IN (?)", status)}
}db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有金额大于 1000 的货到付款订单db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找所有金额大于 1000 且已付款或已发货的订单
四、更新
1. 更新钩子
对于更新操作,GORM 支持 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate 钩子,这些方法将在更新记录时被调用
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {if u.Role == "admin" {return errors.New("admin user not allowed to update")}return
}
2. 阻止全局更新
如果在没有任何条件的情况下执行批量更新,GORM 不会执行该操作,并返回ErrMissingWhereClause错误
您可以使用 = 之类的条件来强制全局更新
db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClausedb.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu" WHERE 1=1
3. 更新的记录数
// 通过 `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 // 更新的记录数
result.Error // 更新的错误
五、删除
1. 删除钩子
对于删除操作,GORM 支持 BeforeDelete、AfterDelete 钩子,在删除记录时会调用这些方法
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {if u.Role == "admin" {return errors.New("admin user not allowed to delete")}return
}
2. 阻止全局删除
如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回ErrMissingWhereClause错误
您可以使用 = 之类的条件来强制全局删除
db.Delete(&User{}).Error // gorm.ErrMissingWhereClausedb.Where("1 = 1").Delete(&User{})
// DELETE `users` WHERE 1=1
3. 软删除
如果您的模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!
拥有软删除能力的模型调用 Delete 时,记录不会被数据库。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;// 批量删除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
如果您不想引入 gorm.Model,您也可以这样启用软删除特性:
type User struct {ID intDeleted gorm.DeletedAtName string
}
4. 查找被软删除的记录
您可以使用 Unscoped 找到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
5. 永久删除
您也可以使用 Unscoped 永久删除匹配的记录
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
六、SQL 构建器
1. 原生 SQL 查询
type Result struct {ID intName stringAge int
}var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)var age int
DB.Raw("select sum(age) from users where role = ?", "admin").Scan(&age)
2. 执行原生 SQL
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})// SQL 表达式
DB.Exec("update users set money=? where name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
3. DryRun 模式
在不执行的情况下生成 SQL ,可以用于准备或测试生成的 SQL
stmt := DB.Session(&Session{DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
stmt.Vars //=> []interface{}{1}
4. Clauses
GORM 内部使用 SQL builder 生成 SQL。对于每个操作,GORM 都会创建一个 *gorm.Statement 对象,所有的 GORM API 都是在为 statement 添加/修改 Clause,最后,GORM 会根据这些 Clause 生成 SQL