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 |
クイックスタート
import ( |
主な機能
このリリースノートは、GORM V2で導入された主要な変更を簡潔な参照リストとしてまとめたものです。
コンテキストサポート
- データベース操作は、`WithContext`メソッドで`context.Context`をサポートします。
- Loggerもトレースのためにコンテキストを受け入れます。
db.WithContext(ctx).Find(&users) |
バッチ挿入
多数のレコードを効率的に挿入するには、`Create`メソッドにスライスを渡します。GORMは、すべてのデータを挿入して主キーの値をバックフィルする単一のSQL文を生成し、フックメソッドも呼び出されます。
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} |
`CreateInBatches`を使用して作成時にバッチサイズを指定できます。例:
var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}} |
プリペアードステートメントモード
プリペアードステートメントモードはプリペアードステートメントを作成し、それらをキャッシュして将来の呼び出しを高速化します。
// globally mode, all operations will create prepared stmt and cache to speed up |
DryRunモード
実行せずにSQLを生成します。生成されたSQLを確認またはテストするために使用できます。
stmt := db.Session(&Session{DryRun: true}).Find(&user, 1).Statement |
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{} |
Create From Map
`map[string]interface{}`または`[]map[string]interface{}`から作成します。
db.Model(&User{}).Create(map[string]interface{}{"Name": "jinzhu", "Age": 18}) |
FindInBatches
レコードをバッチでクエリおよび処理します。
result := db.Where("age>?", 13).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { |
ネストされたトランザクション
db.Transaction(func(tx *gorm.DB) error { |
SavePoint、RollbackTo
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 SubQuery |
Upsert
`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" |
詳細はヒントをご覧ください。
SQL式/コンテキスト値によるCRUD
type Location struct { |
詳細はデータ型のカスタマイズをご覧ください。
フィールド権限
フィールド権限のサポート、権限レベル:読み取り専用、書き込み専用、作成専用、更新専用、無視
type User struct { |
複数のフィールドの作成/更新時刻/UNIX(ミリ/ナノ)秒の追跡
type User struct { |
複数のデータベース、読み書き分離
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{ |
Logger
- コンテキストサポート
- ログの色をカスタマイズ/無効化
- 低速SQLログ、デフォルトの低速SQL時間は200ms
- データベースコンソールでコピーして実行できるように、SQLログ形式を最適化しました。
トランザクションモード
デフォルトでは、すべてのGORM書き込み操作はトランザクション内で実行され、データの一貫性が確保されます。必要ない場合は、初期化時に無効にして書き込み操作を高速化できます。
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ |
データ型(JSONを例として)
GORMはカスタム型のサポートを最適化しているので、すべてのデータベースをサポートする構造体を定義できます。
以下はJSONを例として示しています(SQLite、MySQL、Postgresをサポート、参照:https://github.com/go-gorm/datatypes/blob/master/json.go)
import "gorm.io/datatypes" |
スマートセレクト
GORMでは、`Select`を使用して特定のフィールドを選択できます。V2では、より小さな構造体でクエリを実行する場合、GORMはスマートセレクトモードを提供します。
type User struct { |
関連付けバッチモード
関連付けモードはバッチデータをサポートします。例:
// Find all roles for all users |
削除時の関連付けの削除
レコードを削除するときに、`Select`を使用して選択したHasOne/HasMany/Many2Manyリレーションを削除できます。例:
// delete user's account when deleting user |
破壊的変更
大きな破壊的変更、またはコンパイラで検出できない変更をリストアップしようとしました。リストされていない破壊的変更を見つけた場合は、こちらでissueまたはプルリクエストを作成してください。
タグ
- GORM V2は、タグ名を`camelCase`で記述することを推奨します。`snake_case`のタグは機能しなくなりました(例:`auto_increment`、`unique_index`、`polymorphic_value`、`embedded_prefix`)。モデルタグをご覧ください。
- 外部キーを指定するために使用されるタグは、`foreignKey`、`references`に変更されました。関連付けタグをご覧ください。
- `sql`タグはサポートされなくなりました。
テーブル名
`TableName`は、動的なテーブル名を許容しなくなりました。`TableName`の結果は将来のためにキャッシュされます。
func (User) TableName() string { |
動的なテーブルには`Scopes`を使用してください。例:
func UserTable(u *User) func(*gorm.DB) *gorm.DB { |
テーブルの作成と削除にはMigratorの使用が必要です
以前は、テーブルは次のように作成および削除できました。
db.CreateTable(&MyTable{}) |
現在は、次のようにします。
db.Migrator().CreateTable(&MyTable{}) |
外部キー
外部キー制約を追加する方法の1つは次のとおりです。
db.Model(&MyTable{}).AddForeignKey("profile_id", "profiles(id)", "NO ACTION", "NO ACTION") |
現在は、次のように制約を追加します。
db.Migrator().CreateConstraint(&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{}) |
詳細はメソッドチェーンを参照してください。
デフォルト値
GORM V2では、データベース関数で作成されたデフォルト値は、作成後に自動的に再読み込みされません。デフォルト値を参照してください。
ソフトデリート
GORM V1では、モデルに`DeletedAt`という名前のフィールドがあればソフトデリートが有効になります。V2では、この機能を有効にしたいモデルに対して`gorm.DeletedAt`を使用する必要があります。例:
type User struct { |
注記: `gorm.Model`は`gorm.DeletedAt`を使用しています。`gorm.Model`を埋め込んでいる場合は、変更する必要はありません。
BlockGlobalUpdate
GORM V2では、デフォルトで`BlockGlobalUpdate`モードが有効になっています。グローバルな更新/削除を実行するには、条件を使用するか、生のSQLを使用するか、`AllowGlobalUpdate`モードを有効にする必要があります。例:
db.Where("1 = 1").Delete(&User{}) |
ErrRecordNotFound
GORM V2では、`First`、`Last`、`Take`メソッド(結果の返却が期待されるメソッド)を使用してクエリを実行した場合にのみ`ErrRecordNotFound`が返されます。また、V2では`RecordNotFound`メソッドも削除されました。エラーを確認するには`errors.Is`を使用してください。例:
err := db.First(&user).Error |
Hooksメソッド
V2では、Before/After Create/Update/Save/Find/Deleteは`func(tx *gorm.DB) error`型のメソッドとして定義する必要があります。これは、プラグインコールバックのような統一されたインターフェースを持っています。他の型として定義されている場合、警告ログが出力され、有効になりません。Hooksを参照してください。
func (user *User) BeforeCreate(tx *gorm.DB) error { |
Update Hooksのサポート変更:フィールドの変更有無の確認
`Update`、`Updates`を使用して更新する場合、Hooksの`BeforeUpdate`、`BeforeSave`で`Changed`メソッドを使用して、フィールドの変更有無を確認できます。
func (user *User) BeforeUpdate(tx *gorm.DB) error { |
プラグイン
プラグインコールバックも`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) |
また、`Set("gorm:auto_preload", true)`によるプリロードはGORM V2ではサポートされなくなりました。`Preload`と`clause.Associations`を使用してください。例:
// preload all associations |
グローバルにアソシエーションの作成/更新をスキップするために使用できるフィールド権限についても確認してください。
GORM V2では、レコードの作成/更新時にアソシエーションの保存にupsertを使用し、不完全なデータの保存からデータを保護するために、完全なアソシエーションデータの保存は行わなくなりました。例:
user := User{ |
結合テーブル
GORM V2では、`JoinTable`は`Soft Delete`、`Hooks`などの機能を備えた完全な機能を持つモデルにすることができ、他のフィールドも定義できます。例:
type Person struct { |
その後、通常のGORMメソッドを使用して結合テーブルデータを操作できます。例:
var results []PersonAddress |
Count
Countは`*int64`のみを引数として受け付けます。
トランザクション
`RollbackUnlessCommitted`などのトランザクションメソッドは削除されました。トランザクションをラップするには`Transaction`メソッドを使用してください。
db.Transaction(func(tx *gorm.DB) error { |
トランザクションを参照してください。
Migrator
- Migratorはデフォルトでデータベースの外部キーを作成します。
- Migratorはより独立しており、多くのAPIの名前が変更され、統一されたAPIインターフェースで各データベースをより適切にサポートするように改善されています。
- サイズ、精度、NULL許容の変更があれば、AutoMigrateはカラムの型を変更します。
- `check`タグによるCheckerをサポート
- `index`のタグ設定が強化されました。
マイグレーションを参照してください。
type UserIndex struct { |