Updated GORM V2 Release Note Draft CN (markdown)

Jinzhu 2020-08-28 20:45:55 +08:00
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 为本对象的主键
// 如果定义了many2manyForeignKey 为关联表引用的本对象的主键, 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!