儒雅的煎饼
6 月前 |
gorm.io/gorm
,对于以前的项目,您可以继续使用
github.com/jinzhu/gorm
GORM V1 Document
gorm.io/driver/sqlite
go get gorm.io/gorm |
import ( |
此发布说明仅涵盖了 GORM V2 中的重大更改,作为快速参考
WithContext
方法支持
Context
db.WithContext(ctx).Find(&users) |
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} |
使用
CreateInBatches
创建时,你还可以指定创建的数量,例如:
var 用户 = []User{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}} |
// 全局模式,所有的操作都会创建并缓存预编译语句,以加速后续执行速度 |
DryRun 模式会生成但不执行 SQL,可以用于检查、测试生成的 SQL
stmt := db.Session(&Session{DryRun: true}).Find(&user, 1).Statement |
使用 INNER JOIN 预加载关联,并处理 null 数据避免 scan 失败
db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2}) |
Scan 结果到
map[string]interface{}
或
[]map[string]interface{}
var result map[string]interface{} |
根据
map[string]interface{}
或
[]map[string]interface{}
Create
db.Model(&User{}).Create(map[string]interface{}{"Name": "jinzhu", "Age": 18}) |
result := db.Where("age>?", 13).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { |
db.Transaction(func(tx *gorm.DB) error { |
tx := db.Begin() |
GORM 支持使用
sql.NamedArg
,
map[string]interface{}
作为命名参数
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) |
db.Where( |
// Where 子查询 |
clause.OnConflict
为不同的数据库(SQLite,MySQL,PostgreSQL,SQL Server)提供了兼容的 Upsert 支持
import "gorm.io/gorm/clause" |
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) |
import "gorm.io/hints" |
type Location struct { |
type User struct { |
type User struct { |
GORM 通过
DB Resolver
插件提供了多数据库,读写分离支持。该插件还支持基于当前 struct 和表自动切换数据库和表,自定义负载均衡逻辑的多 source、replica
查看 Database Resolver 获取详情
GORM 提供了
Prometheus
插件来收集
DBStats
和用户自定义指标
查看 Prometheus 获取详情
GORM 允许用户通过覆盖默认的
命名策略
更改默认的命名约定,命名策略被用于构建:
TableName
、
ColumnName
、
JoinTableName
、
RelationshipFKName
、
CheckerName
、
IndexName
。查看
GORM 配置
获取详情
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ |
默认情况下,GORM 所有的写操作都会在事务中运行,以确保数据的一致性。 如果不需要,您可以在初始化时禁用它来加速写入操作
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ |
GORM 优化了对自定义类型的支持,现在您可以定义一个 struct 来支持所有类型的数据库
下面以 JSON 为例(支持 SQLite、MySQL、Postgres。参考自: https://github.com/go-gorm/datatypes/blob/master/json.go )
import "gorm.io/datatypes" |
GORM 可以通过
Select
选择指定的字段,而在 V2 中,通过一个较小的 struct,可以使用 GORM 提供的 smart select 模式
type User struct { |
// 查询所有用户的所有角色 |
你可以在删除记录时通过
Select
来删除具有 has one、has many、many2many 关系的记录,例如:
// 删除 user 时,也删除 user 的 account |
我们尽可能的列出破坏性、无法被编译器捕获的变更。如果您发现了任何遗漏的内容,欢迎在 这里 创建 issue 或 pr
camelCase
风格的 tag 名。
snake_case
风格的 tag 已经失效,例如:
auto_increment
、
unique_index
、
polymorphic_value
、
embeded_prefix
,查看
Model Tag
获取详情
foreignKey
,
references
,查看
Association Tag
获取详情
sql
标签
TableName
不再
允许动态表名, 因为
TableName
的返回值会被缓存下来
func (User) TableName() string { |
动态表名请使用
Scopes
,例如:
func UserTable(u *User) func(*gorm.DB) *gorm.DB { |
db.CreateTable(&MyTable{}) |
现在您需要这样做:
db.Migrator().CreateTable(&MyTable{}) |
db.Model(&MyTable{}).AddForeignKey("profile_id", "profiles(id)", "NO ACTION", "NO ACTION") |
现在您需要这样添加约束:
db.Migrator().CreateConstraint(&Users{}, "Profiles") |
对于postgres,GORM 会将其翻译为:
ALTER TABLE `Profiles` ADD CONSTRAINT `fk_users_profiles` FORIEGN KEY (`useres_id`) REFRENCES `users`(`id`)) |
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) |
创建记录后,GORM V2 不会自动加载由数据库生成的默认值,查看 默认值 获取详情
在 GORM V1 中,如果 model 中有一个名为
DeletedAt
的字段则自动开启软删除。在V2,您需要在想启用软删除的 model 中使用
gorm.DeletedAt
,例如:
type User struct { |
注意:
gorm.Model
使用了gorm.DeletedAt
,如果你已经嵌入了它,则不需要做什么修改BlockGlobalUpdate
db.Where("1 = 1").Delete(&User{})
db.Raw("delete from users")
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})ErrRecordNotFound
err := db.First(&user).Error
errors.Is(err, gorm.ErrRecordNotFound)Hook 方法
在 V2 中,Before/After Create/Update/Save/Find/Delete 必须定义为
func(tx *gorm.DB) error
类型的方法,这是类似于插件 callback 的统一接口。如果定义为其它类型,它不会生效,并且会打印一个警告日志,查看 Hook 获取详情
func (user *User) BeforeCreate(tx *gorm.DB) error {
// 通过 tx.Statement 修改当前操作,例如:
tx.Statement.Select("Name", "Age")
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})
// 除了当前子句,基于 tx 的操作会运行在同一个事务中
var role Role
err := tx.First(&role, "name = ?", user.Role).Error
// SELECT * FROM roles WHERE name = "admin"
return err
}Update Hook 支持
Changed
当使用
Update
,Updates
更新时,您可以在BeforeUpdate
,BeforeSave
Hook 中使用Changed
方法来检查字段是否有更改
func (user *User) BeforeUpdate(tx *gorm.DB) error {
if tx.Statement.Changed("Name", "Admin") { // if Name or Admin changed
tx.Statement.SetColumn("Age", 18)
}
if tx.Statement.Changed() { // 如果任何字段有变动
tx.Statement.SetColumn("Age", 18)
}
return nil
}
db.Model(&user).Update("Name", "Jinzhu") // update field `Name` to `Jinzhu`
db.Model(&user).Updates(map[string]interface{}{"name": "Jinzhu", "admin": false}) // update field `Name` to `Jinzhu`, `Admin` to false
db.Model(&user).Updates(User{Name: "Jinzhu", Admin: false}) // Update none zero fields when using struct as argument, will only update `Name` to `Jinzhu`
db.Model(&user).Select("Name", "Admin").Updates(User{Name: "Jinzhu"}) // update selected fields `Name`, `Admin`,`Admin` will be updated to zero value (false)
db.Model(&user).Select("Name", "Admin").Updates(map[string]interface{}{"Name": "Jinzhu"}) // update selected fields exists in the map, will only update field `Name` to `Jinzhu`
// Attention: `Changed` will only check the field value of `Update` / `Updates` equals `Model`'s field value, it returns true if not equal and the field will be saved
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"}) // Changed("Name") => true
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"}) // Changed("Name") => false, `Name` not changed
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{"name": "jinzhu2", "admin": false}) // Changed("Name") => false, `Name` not selected to update
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"}) // Changed("Name") => true
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"}) // Changed("Name") => false, `Name` not changed
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"}) // Changed("Name") => false, `Name` not selected to update插件
插件 callback 也需要被定义为
func(tx *gorm.DB) error
类型的方法,查看 Write Plugins 获取详情使用 struct 更新
使用 struct 更新时,GORM V2 允许使用
Select
来选择要更新的零值字段,例如:
db.Model(&user).Select("Role", "Age").Update(User{Name: "jinzhu", Role: "", Age: 0})关联
GORM V1允许使用一些设置来跳过 create/update 关联。在 V2 中,您可以使用
Select
来完成这项工作,例如:
db.Omit(clause.Associations).Create(&user)
db.Omit(clause.Associations).Save(&user)
db.Select("Company").Save(&user)此外,GORM V2 不再允许通过
Set("gorm:auto_preload", true)
进行预加载,你可以将Preload
和clause.Associations
配合使用,例如:
// 预加载所有关联
db.Preload(clause.Associations).Find(&users)此外,还可以查看字段权限,它可以用来全局跳过 creating/updating 关联
在创建、更新记录时,GORM V2 将使用 upsert 来保存关联记录。不会再保存完整的关联数据,避免受未完成数据的影响,以保护您的数据,例如:
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "[email protected]"},
{Email: "[email protected]"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}
db.Create(&user)
// BEGIN TRANSACTION;
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "[email protected]"), (111, "[email protected]") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "languages" ("name") VALUES ('ZH'), ('EN') ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "user_languages" ("user_id","language_id") VALUES (111, 1), (111, 2) ON DUPLICATE KEY DO NOTHING;
// COMMIT;Join Table
在 GORM V2 中,
JoinTable
可以是一个带有软删除
、Hook
且定义了其它字段的全功能 model,例如:
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}
type Address struct {
ID uint
Name string
}
type PersonAddress struct {
PersonID int
AddressID int
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}
func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}
// PersonAddress 必须定义好所需的外键,否则会报错
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})然后,您可以使用标准的 GORM 方法来操作连接表的数据,例如:
var results []PersonAddress
db.Where("person_id = ?", person.ID).Find(&results)
db.Where("address_id = ?", address.ID).Delete(&PersonAddress{})
db.Create(&PersonAddress{PersonID: person.ID, AddressID: address.ID})Count
事务
移除了
RollbackUnlessCommitted
之类的事务方法,建议使用Transaction
方法包裹事务
db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// 返回 nil 提交事务
return nil
})Migrator
- Migrator 默认会创建数据库外键
- Migrator 更加独立,重命名了很多 API,以便使用统一 API 接口为每个数据库提供更好的支持
- 如果大小、精度、是否为空可以更改,则 AutoMigrate 会改变列的类型
- 通过
check
标签支持检查器- 增强
index
标签的设置
type UserIndex struct {
Name string `gorm:"check:named_checker,(name <> 'jinzhu')"`
Name2 string `gorm:"check:(age > 13)"`
Name4 string `gorm:"index"`
Name5 string `gorm:"index:idx_name,unique"`
Name6 string `gorm:"index:,sort:desc,collate:utf8,type:btree,length:10,where:name3 != 'jinzhu'"`
}Happy Hacking!