GORM 2.0 发行说明

GORM 2.0 是从头开始重写的,它引入了一些不兼容的 API 更改和许多改进

亮点

  • 性能改进
  • 模块化
  • 支持上下文、批量插入、预处理语句模式、DryRun 模式、连接预加载、查找映射、从映射创建、批量查找
  • 支持嵌套事务/保存点/回滚到保存点
  • 支持 SQL 生成器、命名参数、分组条件、更新插入、锁定、优化器/索引/注释提示,子查询改进,使用 SQL 表达式和上下文值的 CRUD
  • 完全支持自引用关系,连接表改进,批量数据的关联模式
  • 允许多个字段跟踪创建/更新时间,支持 UNIX(毫秒/纳秒)
  • 字段权限支持:只读、只写、仅创建、仅更新、忽略
  • 新的插件系统,为多个数据库、读/写分离、普罗米修斯集成等提供官方插件…
  • 新的钩子 API:与插件的统一接口
  • 新的迁移器:允许为关系创建数据库外键,更智能的自动迁移,约束/检查器支持,增强的索引支持
  • 新的记录器:上下文支持,改进的可扩展性
  • 统一的命名策略:表名、字段名、连接表名、外键、检查器、索引名规则
  • 更好的自定义数据类型支持(例如:JSON)

如何升级

  • GORM 的开发已移至 github.com/go-gorm,其导入路径已更改为 gorm.io/gorm,对于以前的项目,您可以继续使用 github.com/jinzhu/gorm GORM V1 文档
  • 数据库驱动程序已拆分为单独的项目,例如:github.com/go-gorm/sqlite,其导入路径也已更改为 gorm.io/driver/sqlite

安装

go get gorm.io/gorm
// **NOTE** GORM `v2.0.0` released with git tag `v1.20.0`

快速入门

import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)

func init() {
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

// Most CRUD API kept compatibility
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)
}

主要功能

发行说明仅涵盖 GORM V2 中引入的主要更改,作为快速参考列表

上下文支持

  • 数据库操作使用 WithContext 方法支持 context.Context
  • 记录器也接受用于跟踪的上下文
db.WithContext(ctx).Find(&users)

批量插入

要有效地插入大量记录,请将切片传递给 Create 方法。GORM 将生成单个 SQL 语句来插入所有数据并回填主键值,也会调用钩子方法。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

您可以在使用 CreateInBatches 创建时指定批量大小,例如

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// batch size 100
db.CreateInBatches(users, 100)

预处理语句模式

预处理语句模式创建预处理语句并缓存它们以加速未来的调用

// globally mode, all operations will create prepared stmt and cache to speed up
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{PrepareStmt: true})

// session mode, create prepares stmt and speed up current session operations
tx := db.Session(&Session{PrepareStmt: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

DryRun 模式

生成 SQL 而不执行,可用于检查或测试生成的 SQL

stmt := db.Session(&Session{DryRun: true}).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}

连接预加载

使用 INNER JOIN 预加载关联,并将处理空数据以避免扫描失败

db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2})

查找映射

将结果扫描到 map[string]interface{}[]map[string]interface{}

var result map[string]interface{}
db.Model(&User{}).First(&result, "id = ?", 1)

从映射创建

map[string]interface{}[]map[string]interface{} 创建

db.Model(&User{}).Create(map[string]interface{}{"Name": "jinzhu", "Age": 18})

datas := []map[string]interface{}{
{"Name": "jinzhu_1", "Age": 19},
{"name": "jinzhu_2", "Age": 20},
}

db.Model(&User{}).Create(datas)

分批查找

批量查询和处理记录

result := db.Where("age>?", 13).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
// batch processing
return nil
})

嵌套事务

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") // rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx.Create(&user3)
return nil
})

return nil // commit user1 and user3
})

保存点,回滚到

tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // rollback user2

tx.Commit() // commit user1

命名参数

GORM 支持使用 sql.NamedArgmap[string]interface{} 作为命名参数

db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
// SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1

db.Raw(
"SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
sql.Named("name", "jinzhu1"), sql.Named("name2", "jinzhu2"),
).Find(&user)
// SELECT * FROM users WHERE name1 = "jinzhu1" OR name2 = "jinzhu2" OR name3 = "jinzhu1"

db.Exec(
"UPDATE users SET name1 = @name, name2 = @name2, name3 = @name",
map[string]interface{}{"name": "jinzhu", "name2": "jinzhu2"},
)
// UPDATE users SET name1 = "jinzhu", name2 = "jinzhu2", name3 = "jinzhu"

分组条件

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')

子查询

// Where SubQuery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)

// From SubQuery
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18}).Find(&User{})
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE age = 18

// Update SubQuery
db.Model(&user).Update(
"price", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"),
)

插入或更新

clause.OnConflict 为不同的数据库(SQLite、MySQL、PostgreSQL、SQL Server)提供兼容的插入或更新支持

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 name="jinzhu", age=18; 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

锁定

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`

优化器/索引/注释提示

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`)

// Comment Hints
db.Clauses(hints.Comment("select", "master")).Find(&User{})
// SELECT /*master*/ * FROM `users`;

查看提示了解更多详情

从 SQL 表达式/上下文值设定器进行 CRUD 操作

type Location struct {
X, Y int
}

func (loc Location) GormDataType() string {
return "geometry"
}

func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}

db.Create(&User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`point`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

db.Model(&User{ID: 1}).Updates(User{
Name: "jinzhu",
Point: Point{X: 100, Y: 100},
})
// UPDATE `user_with_points` SET `name`="jinzhu",`point`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1

查看自定义数据类型了解更多详情

字段权限

字段权限支持,权限级别:只读、只写、只创建、只更新、忽略

type User struct {
Name string `gorm:"<-:create"` // allow read and create
Name string `gorm:"<-:update"` // allow read and update
Name string `gorm:"<-"` // allow read and write (create and update)
Name string `gorm:"->:false;<-:create"` // createonly
Name string `gorm:"->"` // readonly
Name string `gorm:"-"` // ignored
}

跟踪多个字段的创建时间/更新时间/Unix(毫秒/纳秒)时间戳

type User struct {
CreatedAt time.Time // Set to current time if it is zero on creating
UpdatedAt int // Set to current unix seconds on updaing or if it is zero on creating
Updated int64 `gorm:"autoUpdateTime:nano"` // Use unix Nano seconds as updating time
Updated2 int64 `gorm:"autoUpdateTime:milli"` // Use unix Milli seconds as updating time
Created int64 `gorm:"autoCreateTime"` // Use unix seconds as creating time
}

多数据库,读/写分离

GORM 通过插件 DB Resolver 提供多数据库、读/写分离支持,该插件还支持根据当前结构体/表自动切换数据库/表,以及支持自定义负载均衡逻辑的多源、副本

查看数据库解析器了解更多详情

普罗米修斯

GORM 提供插件 Prometheus 来收集 DBStats 和用户定义的指标

查看普罗米修斯了解更多详情

命名策略

GORM 允许用户通过覆盖默认的 NamingStrategy 来更改默认的命名约定,该策略用于构建 TableNameColumnNameJoinTableNameRelationshipFKNameCheckerNameIndexName,查看GORM 配置了解更多详情

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{TablePrefix: "t_", SingularTable: true},
})

日志记录器

  • 上下文支持
  • 自定义/关闭日志中的颜色
  • 慢 SQL 日志,默认慢 SQL 时间为 200 毫秒
  • 优化了 SQL 日志格式,使其可以复制并在数据库控制台中执行

事务模式

默认情况下,所有 GORM 写操作都在事务内部运行以确保数据一致性,如果不需要,您可以在初始化期间禁用它以加快写操作速度

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})

数据类型(以 JSON 为例)

GORM 优化了对自定义类型的支持,因此您可以定义一个结构体来支持所有数据库

以下以 JSON 为例(支持 SQLite、MySQL、Postgres,参考:https://github.com/go-gorm/datatypes/blob/master/json.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"}}`)),
}

// Query user having a role field in attributes
db.First(&user, datatypes.JSONQuery("attributes").HasKey("role"))
// Query user having orgs->orga field in attributes
db.First(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))

智能选择

GORM 允许使用Select选择特定的字段,在 V2 中,如果您使用较小的结构体进行查询,GORM 提供智能选择模式

type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// Select `id`, `name` automatically when query
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10

关联批量模式

关联模式支持批量数据,例如

// Find all roles for all users
db.Model(&users).Association("Role").Find(&roles)

// Delete User A from all user's team
db.Model(&users).Association("Team").Delete(&userA)

// Get unduplicated count of members in all user's team
db.Model(&users).Association("Team").Count()

// For `Append`, `Replace` with batch data, argument's length need to equal to data's length or will returns error
var users = []User{user1, user2, user3}
// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team
db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})
// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC
db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})

删除记录时删除关联关系

现在,您可以在删除记录时使用 Select 删除选定的一对一/一对多/多对多关系,例如

// delete user's account when deleting user
db.Select("Account").Delete(&user)

// delete user's Orders, CreditCards relations when deleting user
db.Select("Orders", "CreditCards").Delete(&user)

// delete user's has one/many/many2many relations when deleting user
db.Select(clause.Associations).Delete(&user)

// delete user's account when deleting users
db.Select("Account").Delete(&users)

重大变更

我们正在尝试列出重大变更或编译器无法捕获的变更,如果您发现任何未列出的重大变更,请在此处创建问题或拉取请求

标签

  • GORM V2 更喜欢以 camelCase 形式编写标签名称,snake_case 形式的标签将不再有效,例如:auto_incrementunique_indexpolymorphic_valueembedded_prefix,请查看模型标签
  • 用于指定外键的标签已更改为 foreignKeyreferences,请查看关联标签
  • 不再支持 sql 标签

表名

TableName不再允许动态表名,TableName 的结果将被缓存以供将来使用

func (User) TableName() string {
return "t_user"
}

请使用 Scopes 处理动态表,例如

func UserTable(u *User) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Table("user_" + u.Role)
}
}

db.Scopes(UserTable(&user)).Create(&user)

创建和删除表需要使用迁移器

以前可以按如下方式创建和删除表

db.CreateTable(&MyTable{})
db.DropTable(&MyTable{})

现在,您可以执行以下操作

db.Migrator().CreateTable(&MyTable{})
db.Migrator().DropTable(&MyTable{})

外键

添加外键约束的一种方法是:

db.Model(&MyTable{}).AddForeignKey("profile_id", "profiles(id)", "NO ACTION", "NO ACTION")

现在,您可以按如下方式添加约束

db.Migrator().CreateConstraint(&Users{}, "Profiles")
db.Migrator().CreateConstraint(&Users{}, "fk_users_profiles")

这将转换为以下 postgres 的 sql 代码

ALTER TABLE `Profiles` ADD CONSTRAINT `fk_users_profiles` FORIEGN KEY (`useres_id`) REFRENCES `users`(`id`))

方法链安全性/协程安全性

为了减少 GC 分配,GORM V2 在使用方法链时将共享 Statement,并且仅在新初始化的 *gorm.DBNew Session Method 之后创建新的 Statement 实例,要重用 *gorm.DB,您需要确保它在 New Session Method 之后,例如

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

// Safe for new initialized *gorm.DB
for i := 0; i < 100; i++ {
go db.Where(...).First(&user)
}

tx := db.Where("name = ?", "jinzhu")
// NOT Safe as reusing Statement
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user)
}

ctxDB := db.WithContext(ctx)
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user)
}

ctxDB := db.Where("name = ?", "jinzhu").WithContext(ctx)
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user) // `name = 'jinzhu'` will apply to the query
}

tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user) // `name = 'jinzhu'` will apply to the query
}

有关详细信息,请查看方法链

默认值

GORM V2 在创建后不会自动重新加载使用数据库函数创建的默认值,有关详细信息,请查看默认值

软删除

如果模型具有名为 DeletedAt 的字段,则 GORM V1 将启用软删除,在 V2 中,您需要为要启用该功能的模型使用 gorm.DeletedAt,例如

type User struct {
ID uint
DeletedAt gorm.DeletedAt
}

type User struct {
ID uint
// field with different name
Deleted gorm.DeletedAt
}

注意: gorm.Model 正在使用 gorm.DeletedAt,如果您正在嵌入它,则无需进行任何更改

阻止全局更新

GORM V2 默认启用了 BlockGlobalUpdate 模式,要触发全局更新/删除,您必须使用某些条件或使用原始 SQL 或启用 AllowGlobalUpdate 模式,例如

db.Where("1 = 1").Delete(&User{})

db.Raw("delete from users")

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})

ErrRecordNotFound

GORM V2 仅在您使用预期返回某些结果的方法 FirstLastTake 进行查询时才返回 ErrRecordNotFound,并且我们还在 V2 中删除了方法 RecordNotFound,请使用 errors.Is 检查错误,例如

err := db.First(&user).Error
errors.Is(err, gorm.ErrRecordNotFound)

钩子方法

在 V2 中,必须将 Before/After Create/Update/Save/Find/Delete 定义为 func(tx *gorm.DB) error 类型的函数,该函数具有统一的接口,如插件回调,如果定义为其他类型,则会打印警告日志,并且该函数不会生效,有关详细信息,请查看钩子

func (user *User) BeforeCreate(tx *gorm.DB) error {
// Modify current operation through tx.Statement, e.g:
tx.Statement.Select("Name", "Age")
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})

// Operations based on tx will runs inside same transaction without clauses of current one
var role Role
err := tx.First(&role, "name = ?", user.Role).Error
// SELECT * FROM roles WHERE name = "admin"
return err
}

更新钩子支持使用 Changed 检查字段是否已更改

使用 UpdateUpdates 进行更新时,您可以在钩子 BeforeUpdateBeforeSave 中使用 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() { // if any fields 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

插件

插件回调函数也需要定义为 func(tx *gorm.DB) error 类型的函数,详情请查看 编写插件

使用结构体更新

当使用结构体更新时,GORM V2 允许使用 Select 来选择零值字段进行更新,例如

db.Model(&user).Select("Role", "Age").Update(User{Name: "jinzhu", Role: "", Age: 0})

关联关系

GORM V1 允许使用一些设置来跳过创建/更新关联关系,在 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) 进行预加载,您可以将 Preloadclause.Associations 一起使用,例如

// preload all associations
db.Preload(clause.Associations).Find(&users)

另外,请查看字段权限,它可以用于全局跳过创建/更新关联关系

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;

连接表

在 GORM V2 中,JoinTable 可以是一个功能齐全的模型,具有 软删除钩子 等功能,并可以定义其他字段,例如

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 must defined all required foreign keys, or it will raise error
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 只接受 *int64 类型的参数

事务

一些事务方法,如 RollbackUnlessCommitted 已被移除,建议使用 Transaction 方法来包装您的事务

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

详情请查看 事务

迁移工具

  • 迁移工具默认会创建数据库外键
  • 迁移工具更加独立,许多 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'"`
}

祝您编码愉快!

铂金赞助商

黄金赞助商

铂金赞助商

黄金赞助商