GORM 2.0 リリースノート

GORM 2.0はゼロから書き直されたもので、いくつかの非互換APIの変更と多くの改善が導入されています。

ハイライト

  • パフォーマンスの向上
  • モジュール化
  • コンテキスト、バッチ挿入、プリペアードステートメントモード、DryRunモード、Join Preload、Find To Map、Create From Map、FindInBatchesのサポート
  • ネストされたトランザクション/セーブポイント/セーブポイントへのロールバックのサポート
  • SQLビルダー、名前付き引数、グループ条件、Upsert、ロック、オプティマイザ/インデックス/コメントヒントのサポート、サブクエリ改善、SQL式とコンテキスト値によるCRUD
  • 完全な自己参照リレーションシップのサポート、結合テーブルの改善、バッチデータのための関連付けモード
  • 作成/更新時刻の追跡が複数のフィールドで許可されました。UNIX(ミリ/ナノ)秒のサポート。
  • フィールド権限のサポート:読み取り専用、書き込み専用、作成専用、更新専用、無視
  • 新しいプラグインシステム。複数のデータベース、読み書き分離、Prometheus統合のための公式プラグインを提供…
  • 新しいHooks API:プラグインとの統一されたインターフェース
  • 新しいMigrator:リレーションシップのデータベース外部キーの作成、よりスマートなAutoMigrate、制約/チェッカーのサポート、強化されたインデックスサポート
  • 新しいLogger:コンテキストサポート、拡張性の向上
  • 統一されたネーミング戦略:テーブル名、フィールド名、結合テーブル名、外部キー、チェッカー、インデックス名のルール
  • より良いカスタマイズされたデータ型のサポート(例: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`をサポートします。
  • Loggerもトレースのためにコンテキストを受け入れます。
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}

Join Preload

INNER JOINを使用して関連付けをプリロードし、nullデータを処理してスキャンエラーを防ぎます。

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

Find To Map

`map[string]interface{}`または`[]map[string]interface{}`に結果をスキャンします。

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

Create From Map

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

FindInBatches

レコードをバッチでクエリおよび処理します。

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

SavePoint、RollbackTo

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

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

tx.Commit() // commit user1

名前付き引数

GORMは`sql.NamedArg`、`map[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"),
)

Upsert

`clause.OnConflict`は、さまざまなデータベース(SQLite、MySQL、PostgreSQL、SQL Server)で互換性のあるUpsertサポートを提供します。

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`を使用して複数のデータベース、読み書き分離をサポートします。これは、現在の構造体/テーブルに基づいてデータベース/テーブルを自動的に切り替えること、およびカスタマイズされたロードバランシングロジックによる複数のソース/レプリカのサポートも可能です。

詳細はデータベースレゾルバーをご覧ください。

Prometheus

GORMは、`DBStats`とユーザー定義メトリックを収集するためのプラグイン`Prometheus`を提供します。

詳細はPrometheusをご覧ください。

ネーミング戦略

GORMでは、デフォルトの`NamingStrategy`をオーバーライドすることで、デフォルトの命名規則を変更できます。これは、`TableName`、`ColumnName`、`JoinTableName`、`RelationshipFKName`、`CheckerName`、`IndexName`を構築するために使用されます。GORM設定をご覧ください。

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

Logger

  • コンテキストサポート
  • ログの色をカスタマイズ/無効化
  • 低速SQLログ、デフォルトの低速SQL時間は200ms
  • データベースコンソールでコピーして実行できるように、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`を使用して選択したHasOne/HasMany/Many2Manyリレーションを削除できます。例:

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

破壊的変更

大きな破壊的変更、またはコンパイラで検出できない変更をリストアップしようとしました。リストされていない破壊的変更を見つけた場合は、こちらでissueまたはプルリクエストを作成してください。

タグ

  • GORM V2は、タグ名を`camelCase`で記述することを推奨します。`snake_case`のタグは機能しなくなりました(例:`auto_increment`、`unique_index`、`polymorphic_value`、`embedded_prefix`)。モデルタグをご覧ください。
  • 外部キーを指定するために使用されるタグは、`foreignKey`、`references`に変更されました。関連付けタグをご覧ください。
  • `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)

テーブルの作成と削除にはMigratorの使用が必要です

以前は、テーブルは次のように作成および削除できました。

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

現在は、次のようにします。

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

外部キー

外部キー制約を追加する方法の1つは次のとおりです。

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

現在は、次のように制約を追加します。

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

これは、PostgreSQLの場合、次のSQLコードに変換されます。

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

メソッドチェーンの安全性/ゴルーチンの安全性

GCアロケーションを削減するために、GORM V2はメソッドチェーンを使用する際に`Statement`を共有し、新しく初期化された`*gorm.DB`または`New 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では、データベース関数で作成されたデフォルト値は、作成後に自動的に再読み込みされません。デフォルト値を参照してください。

ソフトデリート

GORM V1では、モデルに`DeletedAt`という名前のフィールドがあればソフトデリートが有効になります。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.Model`を埋め込んでいる場合は、変更する必要はありません。

BlockGlobalUpdate

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では、`First`、`Last`、`Take`メソッド(結果の返却が期待されるメソッド)を使用してクエリを実行した場合にのみ`ErrRecordNotFound`が返されます。また、V2では`RecordNotFound`メソッドも削除されました。エラーを確認するには`errors.Is`を使用してください。例:

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

Hooksメソッド

V2では、Before/After Create/Update/Save/Find/Deleteは`func(tx *gorm.DB) error`型のメソッドとして定義する必要があります。これは、プラグインコールバックのような統一されたインターフェースを持っています。他の型として定義されている場合、警告ログが出力され、有効になりません。Hooksを参照してください。

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
}

Update Hooksのサポート変更:フィールドの変更有無の確認

`Update`、`Updates`を使用して更新する場合、Hooksの`BeforeUpdate`、`BeforeSave`で`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)

また、`Set("gorm:auto_preload", true)`によるプリロードはGORM V2ではサポートされなくなりました。`Preload`と`clause.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: "jinzhu@example.com"},
{Email: "jinzhu-2@example.com"},
},
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, "jinzhu@example.com"), (111, "jinzhu-2@example.com") 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`は`Soft Delete`、`Hooks`などの機能を備えた完全な機能を持つモデルにすることができ、他のフィールドも定義できます。例:

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

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

トランザクションを参照してください。

Migrator

  • Migratorはデフォルトでデータベースの外部キーを作成します。
  • Migratorはより独立しており、多くのAPIの名前が変更され、統一されたAPIインターフェースで各データベースをより適切にサポートするように改善されています。
  • サイズ、精度、NULL許容の変更があれば、AutoMigrateはカラムの型を変更します。
  • `check`タグによるCheckerをサポート
  • `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!

プラチナスポンサー

ゴールドスポンサー

プラチナスポンサー

ゴールドスポンサー