mirror of
https://github.com/go-gorm/gorm.git
synced 2025-01-23 10:50:28 +08:00
Updated GORM V2 Release Note Draft CN (markdown)
parent
d38f746f9c
commit
b1ea1776f9
@ -1,621 +1,2 @@
|
||||
# 访问 https://gorm.io/zh_CN/docs/v2_release_note.html 获取最新版
|
||||
|
||||
---
|
||||
# GORM 2.0 Release Note (Draft)
|
||||
|
||||
GORM 2.0 版是基于用户过去几年中的反馈进行思考后的重写,在该发行版本中将会引入不兼容 API 改动。
|
||||
|
||||
GORM 2.0 现处于公测阶段,已部署于多个用户的生产服务环境之中,欢迎大家测试并提交反馈,以实现更完善的 GORM V2! 如无意外,2.0 正式版将在 2万 star 时正式发布! ;)
|
||||
|
||||
(本文为草稿文案,仍在更新中... 本文仅包含部分较重要更新,详情请参阅文档,V2 详细文档仍在重写进行中)
|
||||
|
||||
**主要更新:**
|
||||
|
||||
* 性能优化
|
||||
* 代码模块化
|
||||
* Context、批量插入、Prepared Statment、DryRun 模式、Join Preload, Find 到 Map, FindInBatches 支持
|
||||
* SavePoint/RollbackTo/Nested Transaction 支持
|
||||
* 关联关系优化 (On Delete/Update),多对多中间表替换优化,Association 模式对批量数据支持及优化
|
||||
* SQL Builder 优化, Upsert, Locking, Optimizer/Index/Comment Hints 支持
|
||||
* 插入时间、更新时间可支持多个字段,加入了对 unix (nano) second 的支持
|
||||
* 字段多重权限支持:只读、只允许创建、只允许更新、只写、完全忽略
|
||||
* 全新的 Migrator, Logger
|
||||
* 统一命名策略系统 (统一的设置表名、字段名、join table、外键、约束、索引名规则)
|
||||
* 更好的数据类型定义支持 (例如 JSON)
|
||||
* 全新插件系统, Hooks API
|
||||
|
||||
|
||||
## 如何升级?
|
||||
|
||||
* GORM 代码已迁移至组织 [github.com/go-gorm](https://github.com/go-gorm), go import 地址更改为 `gorm.io/gorm`,如需继续使用原版本请保持引用 `github.com/jinzhu/gorm`
|
||||
* 为方便扩展数据库支持,添加新的 driver,不同数据库 driver 已分拆为独立项目,例如 [github.com/go-gorm/sqlserver](https://github.com/go-gorm/sqlserver)
|
||||
|
||||
#### 安装
|
||||
|
||||
```sh
|
||||
go get gorm.io/gorm@v0.2.20
|
||||
```
|
||||
|
||||
#### 使用
|
||||
|
||||
```go
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlserver"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 初始化 gorm DB 时,可以通过 Config 修改配置,例如 NowFunc,不再允许通过全局变量方式进行修改,后续介绍中还包含部分其它选项介绍
|
||||
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{NowFunc: func() time.Time { return time.Now().Local() }})
|
||||
db, err := gorm.Open(mysql.Open("gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True"), &gorm.Config{})
|
||||
db, err := gorm.Open(postgres.Open("user=gorm password=gorm dbname=gorm port=9920 TimeZone=Asia/Shanghai"), &gorm.Config{})
|
||||
|
||||
// 基本 CRUD API 尽量保持了兼容,大多数 API 与之前版本用法相同,详情请参考文档
|
||||
db.AutoMigrate(&Product{})
|
||||
db.Create(&user)
|
||||
db.First(&user, 1)
|
||||
db.Model(&user).Update("Age", 18)
|
||||
db.Model(&user).Omit("Role").Updates(map[string]interface{}{"Name": "jinzhu", "Role": "admin"})
|
||||
db.Delete(&user)
|
||||
}
|
||||
```
|
||||
|
||||
## 主要更新介绍
|
||||
|
||||
### Context 支持
|
||||
|
||||
* 所有 SQL 操作都提供了 context 支持
|
||||
|
||||
```go
|
||||
// 单会话模式设置 Context
|
||||
DB.WithContext(ctx).Find(&users)
|
||||
|
||||
// 连续会话模式设置 Context
|
||||
tx := DB.WithContext(ctx)
|
||||
tx.First(&user, 1)
|
||||
tx.Model(&user).Update("role", "admin")
|
||||
```
|
||||
|
||||
* `Logger` 也将接受 context,方便日志追踪处理
|
||||
|
||||
### 批量数据插入
|
||||
|
||||
* 将 slice 数据传入 `Create` 创建时,会创建单条 SQL 语句以插入全部数据,并将主键值回填
|
||||
* 如果批量数据包含关联对象等,也会创建一条 `SQL` 将关联中的*新*对象进行插入
|
||||
* 批量插入的数据都会调用他们定义的 `Hooks` (Before/After Create/Save) 相关方法
|
||||
|
||||
```go
|
||||
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
|
||||
DB.Create(&users)
|
||||
|
||||
for _, user := range users {
|
||||
user.ID // 1,2,3
|
||||
}
|
||||
```
|
||||
|
||||
### Prepared Statment 模式
|
||||
|
||||
* Prepared Statement 模式中,将对执行的 `SQL` 生成 prepared statement 并缓存,以加速后续同样操作的执行性能
|
||||
* Prepared Statement 模式可以全局或者针对会话打开
|
||||
|
||||
```go
|
||||
// 在初始化时启用全局 Prepared Statement 模式,所有后续操作都会进行启动缓存
|
||||
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{PrepareStmt: true})
|
||||
|
||||
// 连续会话模式
|
||||
tx := DB.Session(&Session{PrepareStmt: true})
|
||||
tx.First(&user, 1)
|
||||
tx.Find(&users)
|
||||
tx.Model(&user).Update("Age", 18)
|
||||
```
|
||||
|
||||
### DryRun 模式
|
||||
|
||||
根据当前的 driver 生成相应的 `SQL` 但不执行,方便检查、测试生成的 SQL
|
||||
|
||||
```go
|
||||
// 全局模式
|
||||
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{DryRun: true})
|
||||
|
||||
stmt := db.Find(&user, 1).Statement
|
||||
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 // PostgreSQL
|
||||
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = ? // MySQL
|
||||
stmt.Vars //=> []interface{}{1}
|
||||
|
||||
// 会话模式
|
||||
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}
|
||||
```
|
||||
|
||||
### Join Preload 模式
|
||||
|
||||
* 之前可通过 `Preload` 方法预加载关联时,其会产生 2 条 SQL 语句来查询数据,通过 `Join` 模式,将只会产生一条 `Join SQL` 加载数据
|
||||
* Join Preload 也可用于查询批量数据,会自动处理 `NULL` 值问题
|
||||
|
||||
```go
|
||||
DB.Joins("Company").Joins("Manager").Joins("Account").First(&user, 1)
|
||||
DB.Joins("Company").Joins("Manager").Joins("Account").First(&user, "users.name = ?", "jinzhu")
|
||||
DB.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2,3,4,5})
|
||||
```
|
||||
|
||||
### Find To Map
|
||||
|
||||
将查询结果查询到 `map[string]interface{}` 或者 `[]map[string]interface{}` 中,方便某些情况下的查询
|
||||
|
||||
```go
|
||||
var result map[string]interface{}
|
||||
DB.Model(&User{}).First(&result, "id = ?", 1)
|
||||
|
||||
var results []map[string]interface{}
|
||||
DB.Model(&User{}).Find(&results)
|
||||
```
|
||||
|
||||
### FindInBatches
|
||||
|
||||
批量查询数据并处理
|
||||
|
||||
```go
|
||||
result := DB.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
|
||||
// 批量数据处理
|
||||
for _, result := range results {
|
||||
result.Processed = true
|
||||
}
|
||||
DB.Save(&results)
|
||||
|
||||
tx.RowsAffected // 返回本批查询到的数据的数量
|
||||
|
||||
batch // 第几批次, 1, 2, 3
|
||||
|
||||
// 返回错误中止后续批量处理
|
||||
return nil
|
||||
})
|
||||
|
||||
result.Error // 本次操作返回的错误
|
||||
result.RowsAffected // 本次操作所有数据的数量
|
||||
```
|
||||
|
||||
### SavePoint, RollbackTo 支持
|
||||
|
||||
```go
|
||||
tx := DB.Begin()
|
||||
tx.Create(&user1)
|
||||
|
||||
tx.SavePoint("sp1")
|
||||
tx.Create(&user2)
|
||||
tx.RollbackTo("sp1") // 回滚用户2的创建
|
||||
|
||||
tx.Commit() // Commit 用户1的创建
|
||||
```
|
||||
|
||||
### Nested Transaction 支持
|
||||
|
||||
```go
|
||||
DB.Transaction(func(tx *gorm.DB) error {
|
||||
tx.Create(&user1)
|
||||
|
||||
tx.Transaction(func(tx2 *gorm.DB) error {
|
||||
tx.Create(&user2)
|
||||
return errors.New("rollback user2") // 回滚用户2的创建
|
||||
})
|
||||
|
||||
tx.Transaction(func(tx2 *gorm.DB) error {
|
||||
tx.Create(&user3)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Commit 用户1, 用户3的创建
|
||||
```
|
||||
|
||||
### 关联关系支持优化
|
||||
|
||||
* 对于 Belongs To, 单表 Belongs To, Has One, Has One Polymorphic, Has Many, Has Many Polymorphic, 单表 Has Many, Many2Many, 单表 Many2Many 支持重新进行了实现,避免部分单表情况下判断关联、外键错误
|
||||
* 简化通过 `tag` 设置外键逻辑 (仅在没有使用默认命名规则时,才需要通过 tag 设置)
|
||||
|
||||
```go
|
||||
// 如果 ForeignKey 的字段为本对象所有时,为 Belongs To 关系, References 为关联对象的主键
|
||||
// 如果 ForeignKey 的字段为关联对象所有时,为 Has One/Many 关系, References 为本对象的主键
|
||||
// 如果定义了many2many,ForeignKey 为关联表引用的本对象的主键, JoinForeignKey 为关联表与本对象间的外键,
|
||||
// References 为关联表引用的关联对象的主键, JoinReferences 为关联表与关联对象间的外键
|
||||
// 如果有设置多个外键,可以用逗号隔开
|
||||
|
||||
type Profile struct {
|
||||
gorm.Model
|
||||
Refer string
|
||||
Name string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Profile Profile `gorm:"ForeignKey:ProfileID;References:Refer"`
|
||||
ProfileID int
|
||||
}
|
||||
|
||||
// 多个主键的 Many2Many 关联
|
||||
type Tag struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Locale string `gorm:"primary_key"`
|
||||
Value string
|
||||
}
|
||||
|
||||
type Blog struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Locale string `gorm:"primary_key"`
|
||||
Subject string
|
||||
Body string
|
||||
// 默认用使用对象的全部主键 (ID, Locale) 来创建关联表
|
||||
Tags []Tag `gorm:"many2many:blog_tags;"`
|
||||
// 对于 Blog, Tag 都将只使用 ID 做为主键
|
||||
SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
|
||||
// 对于 Blog 使用ID, Locale作为主键, Tag 只使用ID做为主键
|
||||
LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;References:id"`
|
||||
}
|
||||
```
|
||||
|
||||
#### 关联 On Delete/Update 支持
|
||||
|
||||
```go
|
||||
type Profile struct {
|
||||
gorm.Model
|
||||
Refer string
|
||||
Name string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Profile Profile `gorm:"Constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
ProfileID int
|
||||
}
|
||||
```
|
||||
|
||||
#### Select, Omit, Preload 对 clause.Associations 支持
|
||||
|
||||
```go
|
||||
// 在查询时加载所有的关联
|
||||
DB.Preload(clause.Associations).First(&user)
|
||||
|
||||
// 在保存时跳过所有的关联的保存
|
||||
DB.Omit(clause.Associations).Create(&user)
|
||||
```
|
||||
|
||||
### 多对多中间表替换优化
|
||||
|
||||
可以更简单的来设置 `Many2Many` 的 `JoinTable`,替换后的 `JoinTable` 可获得普通模型的全部功能,例如 `Soft Delete`,`Hooks` 等功能支持,也可以添加更多字段
|
||||
|
||||
```go
|
||||
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 必须包含 Many2Many 所需要的外键字段,否则会返回错误
|
||||
err := DB.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
|
||||
```
|
||||
|
||||
### Association 模式
|
||||
|
||||
* 删掉了 `Related` 方法,避免用户发生未指定或指定了错误的外键导致的错误,请使用 `gorm.Model(&user).Association("Pets").Find(&pets)` 替换 `Related` 方法
|
||||
* `Association` 加入了对批量数据的支持,例如:
|
||||
|
||||
```go
|
||||
// 找出所有用户包含的全部角色
|
||||
gorm.Model(&users).Association("Role").Find(&roles)
|
||||
|
||||
// 把用户A 从所有用户拥有的 Team 中删除
|
||||
gorm.Model(&users).Association("Team").Delete(&userA)
|
||||
|
||||
// 查询所有用户中的 team 一共有多少人员 (不计重复人员)
|
||||
gorm.Model(&users).Association("Team").Count()
|
||||
|
||||
// 对于 Append, Replace 方法,如果想操作批量数据,输入参数的长度必须与批量数据的长度相同
|
||||
// 例如:有三个用户,将 userA 添加到 user1 组,userB 添加到 user2 组,userA, userB, userC 添加到 user3 组
|
||||
var users = []User{user1, user2, user3}
|
||||
gorm.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})
|
||||
// 将 user1 的组员重置为只有 userA,将 user2 组员重置为 userB, 将 user3 组员重置为 userA, userB, userC
|
||||
gorm.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})
|
||||
```
|
||||
|
||||
### 插入时间、更新时间可支持多个字段,加入了对 unix (nano) second 的支持 (设置为int类型)
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
CreatedAt time.Time // 创建对象时如果该字段无值,再更新为当前时间
|
||||
UpdatedAt int // 更新对象时,将该字段更新为当前 unix second),创建对象时如果该字段无值,更新为当前时间
|
||||
Updated int64 `gorm:"autoupdatetime:nano"` // 为自定义字段使用 unix nano second 做为更新时间
|
||||
Created int64 `gorm:"autocreatetime"` // 为自定义字段使用 unix second 做为插入时间
|
||||
}
|
||||
```
|
||||
|
||||
### 字段多重权限支持: 只读、只允许创建、只允许更新、只写、完全忽略
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Name string `gorm:"<-:create"` // 允许从数据库读取,创建时插入到数据库,更新时将忽略该字段
|
||||
Name string `gorm:"<-:update"` // 允许从数据库读取,更新时更新到数据库,创建时将忽略该字段
|
||||
Name string `gorm:"<-"` // 允许从数据库读取,并且创建,更新时插入到数据库
|
||||
Name string `gorm:"->:false;<-:create"` // 允许创建时插入到数据库,更新时、查询时将忽略该字段
|
||||
Name string `gorm:"->"` // 允许从数据库读取,不允许创建、更新
|
||||
Name string `gorm:"-"` // 不允许用户读取,创建及更新
|
||||
}
|
||||
```
|
||||
|
||||
### 全新的Migrator
|
||||
|
||||
* Migrator 在创建表时将会自动创建数据库的外键 (初始化时使用 `DisableForeignKeyConstraintWhenMigrating` 可取消自动创建) (在使用 `DropTable` 删除表时会忽略/删除相关外键约束,来避免删除失败)
|
||||
* Migrator 更加独立,对各数据库提供了更好的支持,保证统一的 API 接口,因此可基于其设计更方便的 `Migrator Tool` (例如: sqlite 原生不支持 `ALTER COLUMN`, `DROP COLUMN`,在做相关操作时,会在事务内使用修改后的表信息新建临时表,导入原数据,并在删除原表后,将临时表重命名为原表名)
|
||||
* 支持通过 Tag 设置 Check 约束
|
||||
* 更丰富的 Tag 设置索引选项
|
||||
|
||||
```go
|
||||
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'"`
|
||||
}
|
||||
```
|
||||
|
||||
### 统一的 NamingStrategy
|
||||
|
||||
可以在初始化数据库时设置统一的命名逻辑接口 (字段名,表名,join table名,外键,Check 约束, 索引名),方便修改命名规则 (例如添加表名前缀),详情参考: [https://github.com/go-gorm/gorm/blob/master/schema/naming.go#L14](https://github.com/go-gorm/gorm/blob/master/schema/naming.go#L14)
|
||||
|
||||
```go
|
||||
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{TablePrefix: "t_", SingularTable: true},
|
||||
})
|
||||
```
|
||||
|
||||
### Logger
|
||||
|
||||
Logger 除了上面提到的 context 支持,还做了如下优化
|
||||
|
||||
* 自定义/关掉 log 中的颜色
|
||||
* 加入 Slow SQL 的显示,Slow SQL 默认时长 100ms
|
||||
* 优化了 log 在不同数据库生成的 SQL 格式,使其可以复制粘帖执行
|
||||
|
||||
### 安全更新,安全删除。如果没提供查询条件,将禁止更新,删除
|
||||
|
||||
```go
|
||||
DB.Delete(&User{}) // 返回错误
|
||||
DB.Model(&User{}).Update("role", "admin") // 返回错误
|
||||
|
||||
DB.Where("1=1").Delete(&User{}) // 全部删除
|
||||
DB.Model(&User{}).Where("1=1").Update("role", "admin") // 全部更新
|
||||
```
|
||||
|
||||
### 事务模式
|
||||
|
||||
默认 GORM 的更新,插入,删除等操作都在一个事务中,以保证数据的一致性,因此可在 `Hooks` 中检查错误时返回错误,相应操作将被 rollback。不过对于不需要该功能的应用程序来说会影响部分性能,新版本在初始化阶段提供关闭相应功能的选项
|
||||
|
||||
```go
|
||||
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{SkipDefaultTransaction: true})
|
||||
```
|
||||
|
||||
### Hooks 方法
|
||||
|
||||
Before/After Create/Update/Save/Find/Delete 等钩子必须定义为 `func(tx *gorm.DB) error` 类型的方法, 如果定义为其它类型,会打印警告日志,并且不会生效
|
||||
|
||||
```go
|
||||
func (user *User) BeforeCreate(tx *gorm.DB) error {
|
||||
// 可以通过 db.Statement 修改当前操作
|
||||
tx.Statement.Select("Name", "Age")
|
||||
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})
|
||||
|
||||
// 使用传入的 *gorm.DB 执行的新操作将会与同前操作在同一事务内,但不会拥有的当前操作定义的 clauses
|
||||
var role Role
|
||||
err := tx.First(&role, "name = ?", user.Role).Error // SELECT * FROM roles WHERE name = "admin"
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新时支持 Changed 检查某字段是否修改
|
||||
|
||||
在使用 `Update`, `Updates` 更新数据时,在 `BeforeUpdate`, `BeforeSave` 中可以使用 `Changed` 方法来检查某字段是否修改
|
||||
|
||||
```go
|
||||
func (user *User) BeforeUpdate(tx *gorm.DB) error {
|
||||
if tx.Statement.Changed("Name", "Admin") { // 如果 Name 或者 Admin 被修改
|
||||
tx.Statement.SetColumn("Age", 18)
|
||||
}
|
||||
|
||||
if tx.Statement.Changed() { // 如果任意字段被修改
|
||||
tx.Statement.SetColumn("Age", 18)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
DB.Model(&user).Update("Name", "Jinzhu") // 更新 Name 为 Jinzhu
|
||||
DB.Model(&user).Updates(map[string]interface{}{"name": "Jinzhu", "admin": false}) // 更新 Name 为 Jinzhu, Admin 更新为 false
|
||||
DB.Model(&user).Updates(User{Name: "Jinzhu", Admin: false}) // 更新 Updates 中字段为非零值的字段,此时只更新 Name 为 Jinzhu
|
||||
|
||||
DB.Model(&user).Select("Name", "Admin").Updates(User{Name: "Jinzhu"}) // 更新 Name, Admin 字段,Admin 字段将被更新为空值 (false)
|
||||
DB.Model(&user).Select("Name", "Admin").Updates(map[string]interface{}{"Name": "Jinzhu"}) // 只更新 Name 为 Jinzhu, Admin 不在 map 中存在,不会被更新
|
||||
|
||||
// 注意:Changed 方法仅判断 `Update` / `Updates` 参数中的值与 `Model` 值的中的同样字段的值是否相同并且是否会被更新,如果不同并会被更新再返回 true
|
||||
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 值未更新
|
||||
DB.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{"name": "jinzhu2", "admin": false}) // Changed("Name") => false, Name 值未被选择更新
|
||||
|
||||
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 值未更新
|
||||
DB.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"}) // Changed("Name") => false, Name 值未被选择更新
|
||||
```
|
||||
|
||||
### TableName 方法
|
||||
|
||||
TableName 方法将*不*再支持动态的表名,避免一系列容易出错的问题。`TableName` 的返回值将会在第一次执行时缓存给后续该类对象使用
|
||||
|
||||
```go
|
||||
func (User) TableName() string {
|
||||
return "t_user"
|
||||
}
|
||||
```
|
||||
|
||||
### 更好的自定义数据类型支持 (JSON 为例)
|
||||
|
||||
GORM 优化对了自定义类型的支持,因此可以定义一个数据结构来支持所有的数据库。
|
||||
|
||||
下面以 JSON 为例 (已支持 mysql, postgres, 代码参考: https://github.com/go-gorm/datatypes/blob/master/json.go)
|
||||
|
||||
```go
|
||||
import "gorm.io/datatypes"
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Attributes datatypes.JSON
|
||||
}
|
||||
|
||||
DB.Create(&User{
|
||||
Name: "jinzhu",
|
||||
Attributes: datatypes.JSON([]byte(`{"name": "jinzhu", "age": 18, "tags": ["tag1", "tag2"], "orgs": {"orga": "orga"}}`)),
|
||||
}
|
||||
|
||||
// 查询用户的 attributes 有没有 role 字段
|
||||
DB.First(&user, datatypes.JSONQuery("attributes").HasKey("role"))
|
||||
// 查询用户的 attributes 有没有 orgs->orga 字段
|
||||
DB.First(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))
|
||||
```
|
||||
|
||||
### Omit, Select 不强制要求传入slice, 可传入多个参数
|
||||
|
||||
```go
|
||||
// 创建对象时
|
||||
DB.Select("Name", "Age").Create(&user) // 使用 Select 指定的字段创建对象
|
||||
DB.Omit([]string{"Name", "Age"}).Create(&user) // 忽略 Omit 指定的字段创建对象
|
||||
|
||||
// 更新对象时
|
||||
DB.Model(&user).Select("Name", "Age").Updates(map[string]interface{}{"name": "jinzhu", "age": 18, "role": "admin"})
|
||||
DB.Model(&user).Omit([]string{"Role"}).Update(User{Name: "jinzhu", Role: "admin"})
|
||||
```
|
||||
|
||||
### SQL Builder
|
||||
|
||||
* GORM 内部采取了 SQL Builder 的方式来生成执行的 `SQL`
|
||||
* 在执行一次操作过程中,GORM 会创建一个 `Statement` 对象,所有的 API 都将为这个 `Statement` 添加 `Clause`, 最终 GORM 将会在 callbacks 中根据这次 `Statement` 所包含的 `Clauses` 生成最终 `SQL` 语句并执行
|
||||
* 对于不同的数据库来说,相同的 Clause 可能会生成的不同的 SQL
|
||||
|
||||
#### Group Conditions
|
||||
|
||||
```go
|
||||
db.Where(
|
||||
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
|
||||
).Or(
|
||||
db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
|
||||
).Find(&pizzas)
|
||||
|
||||
// SELECT * FROM pizzas WHERE (pizza = 'pepperoni' AND (size = 'small' OR size = 'medium')) OR (pizza = 'hawaiian' AND size = 'xlarge')
|
||||
```
|
||||
|
||||
#### Upsert
|
||||
|
||||
GORM 通过 `clause.OnConflict` 对不同数据库 (Sqlite, MySQL, PostgreSQL, SQL Server) 提供了兼容的 Upsert 支持
|
||||
|
||||
```go
|
||||
import "gorm.io/gorm/clause"
|
||||
|
||||
DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&users)
|
||||
|
||||
DB.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.Assignments(map[string]interface{}{"name": "jinzhu", "age": 18}),
|
||||
}).Create(&users)
|
||||
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
|
||||
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL
|
||||
|
||||
DB.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
|
||||
}).Create(&users)
|
||||
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
|
||||
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
|
||||
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL
|
||||
```
|
||||
|
||||
#### Locking
|
||||
|
||||
```go
|
||||
DB.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) // SELECT * FROM `users` FOR UPDATE
|
||||
|
||||
DB.Clauses(clause.Locking{
|
||||
Strength: "SHARE",
|
||||
Table: clause.Table{Name: clause.CurrentTable},
|
||||
}).Find(&users)
|
||||
// SELECT * FROM `users` FOR SHARE OF `users`
|
||||
```
|
||||
|
||||
#### Optimizer/Index/Comment Hints
|
||||
|
||||
```go
|
||||
import "gorm.io/hints"
|
||||
|
||||
// Optimizer Hints
|
||||
DB.Clauses(hints.New("hint")).Find(&User{})
|
||||
// SELECT * /*+ hint */ FROM `users`
|
||||
|
||||
// Index Hints
|
||||
DB.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
|
||||
// SELECT * FROM `users` USE INDEX (`idx_user_name`)
|
||||
|
||||
DB.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
|
||||
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"
|
||||
|
||||
DB.Clauses(
|
||||
hints.ForceIndex("idx_user_name", "idx_user_id").ForOrderBy(),
|
||||
hints.IgnoreIndex("idx_user_name").ForGroupBy(),
|
||||
).Find(&User{})
|
||||
// SELECT * FROM `users` FORCE INDEX FOR ORDER BY (`idx_user_name`,`idx_user_id`) IGNORE INDEX FOR GROUP BY (`idx_user_name`)"
|
||||
|
||||
// Comment Hints
|
||||
DB.Clauses(hints.Comment("select", "master")).Find(&User{})
|
||||
// SELECT /*master*/ * FROM `users`;
|
||||
|
||||
DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
|
||||
// /*node2*/ INSERT INTO `users` ...;
|
||||
|
||||
DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
|
||||
// /*node2*/ INSERT INTO `users` ...;
|
||||
|
||||
DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
|
||||
// SELECT * FROM `users` WHERE id = ? /* hint */
|
||||
```
|
||||
|
||||
### 全新插件API
|
||||
|
||||
GORM 将不同数据库的实现进行了分拆,在使用 `gorm.Open(dialector, &gorm.Config{})` 初始化 `gorm.DB` 时,需要数据库 Driver 实现 `gorm.Dialector` 接口后做为参数传入
|
||||
|
||||
在初始化时,需要数据库的 driver 给 `*gorm.DB` 注册、修改、删除 `Callbacks`, 具体包含 `create`, `query`, `update`, `delete`, `row`, `raw` 等类型, 他们的方法类型为 `func(db *gorm.DB)`,详情请参考:
|
||||
|
||||
* Sqlite 初始化 \*gorm.DB: [https://github.com/go-gorm/sqlite/blob/master/sqlite.go](https://github.com/go-gorm/sqlite/blob/master/sqlite.go)
|
||||
* 注册 Callbacks 过程: [https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go](https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go)
|
||||
|
||||
在所注册的 callbacks 中需要某个 `callback` 方法读取当前操作的 `Statement` 中定义的 `Clauses` 并生成相应的操作并执行,其它的 callbacks 可以为当前的 statement 添加、修改 Clause,或者判断错误等
|
||||
|
||||
实现详情请参考 [https://github.com/go-gorm/gorm/tree/master/callbacks](https://github.com/go-gorm/gorm/tree/master/callbacks)
|
||||
|
||||
|
||||
# Happy Hacking!
|
||||
## 访问 https://gorm.io/zh_CN/docs/v2_release_note.html 获取最新版
|
||||
## Happy Hacking!
|
||||
|
Loading…
Reference in New Issue
Block a user