Gorm データベース ORM ライブラリ
公式ドキュメント:GORM - The fantastic ORM library for Golang, aims to be developer friendly.
リポジトリ:go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)
Go コミュニティでは、データベース操作に関して 2 つの陣営があります。1 つ目の陣営は sqlx のようなシンプルなライブラリを好み、強力ではありませんが、SQL を完全に制御でき、パフォーマンスを極限まで最適化できます。もう 1 つの陣営は ORM を開発効率のために好み、開発中に多くの不必要な手間を省くことができます。ORM と言えば、Go 言語コミュニティで絶対に避けられないのは gorm です。これは非常に成熟した ORM で、xorm、ent などの比較的若いものと似ています。この記事は gorm についてです。この記事では基本的な入門内容のみを説明し、出発点としてください。より詳細な内容を学ぶには、公式ドキュメントを読むことができます。中国語ドキュメントは非常に完全で、著者も gorm ドキュメントの翻訳者の一人です。
特徴
- 全機能 ORM
- 関連付け(Has One、Has Many、Belongs To、Many To Many、ポリモーフィズム、単一テーブル継承)
- フック(Before/After Create/Save/Update/Delete/Find)
- 先読みと先行読み込み(Preload、Joins)
- トランザクション、ネストされたトランザクション、セーブポイント、保存ポイントへのロールバック
- コンテキスト、プリペアドステートメントモード、DryRun モード
- バッチ挿入、FindInBatches、マップでの Find/Create、SQL 式での CRUD、コンテキスト Valuer
- SQL ビルダー、Upsert、ロック、オプティマイザー/インデックス/コメントヒント、名前付きパラメータ、サブクエリ
- 複合主キー、インデックス、制約
- 自動マイグレーション
- カスタムロガー
- 柔軟で拡張可能なプラグイン API:データベースリゾルバー(複数データベース、読み取り/書き込み分離)、Prometheus...
- すべての機能が厳密にテストされています
- 開発者フレンドリー
Gorm にはいくつかの短所もあります。例えば、ほぼすべてのメソッドパラメータが空のインターフェース型で、ドキュメントを見なければ何を渡せばよいかわからないことがあります。時には構造体を渡せたり、時には文字列、時にはマップ、時にはスライスを渡せます。セマンティクスはやや曖昧で、多くの場合まだ手動で SQL を書く必要があります。
代替案として、試せる 2 つの ORM があります。1 つ目は aorm で、それほど前にオープンソース化されました。もはやテーブルフィールド名を手動で書く必要がなく、主にチェーン操作を使用し、リフレクションに基づいています。スター数が多くないので、様子見できます。2 つ目は facebook がオープンソース化した ent です。チェーン操作もサポートしており、ほとんどの場合手動で SQL を書く必要がありません。設計哲学はグラフ(データ構造の)に基づいており、実装はリフレクションではなくコード生成に基づいています(これにはより同意します)。しかし、ドキュメントはすべて英語で、ある程度の学習ハードルがあります。
インストール
gorm ライブラリのインストール
$ go get -u gorm.io/gorm接続
Gorm は現在以下のデータベースをサポートしています
- MySQL:
"gorm.io/driver/mysql" - PostgreSQL:
"gorm.io/driver/postgres" - SQLite:
"gorm.io/driver/sqlite" - SQL Server:
"gorm.io/driver/sqlserver" - TIDB:
"gorm.io/driver/mysql"、TIDB は mysql プロトコルと互換性があります - ClickHouse:
"gorm.io/driver/clickhouse"
さらに、サードパーティ開発者によって提供される他のデータベースドライバーもあります。例えば oracle ドライバー CengSin/oracle です。この記事では MySQL をデモンストレーションに使用します。使用するデータベースに関係なく、対応するドライバーをインストールする必要があります。ここでは MySQL の gorm ドライバーをインストールします。
$ go get -u gorm.io/driver/mysqlその後、dsn(データソース名)を使用してデータベースに接続します。ドライバーライブラリは自動的に dsn を対応する設定に解析します。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log/slog"
)
func main() {
dsn := "root:123456@tcp(192.168.48.138:3306)/hello?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
slog.Error("db connect error", err)
}
slog.Info("db connect success")
}または手動で設定を渡します
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log/slog"
)
func main() {
db, err := gorm.Open(mysql.New(mysql.Config{}))
if err != nil {
slog.Error("db connect error", err)
}
slog.Info("db connect success")
}どちらの方法も同等です。使用の好みに依存します。
接続設定
gorm.Config 設定構造体を渡すことで、gorm のいくつかの動作を制御できます。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})以下にいくつかの簡単な説明を示します。必要に応じて設定できます。
type Config struct {
// デフォルトトランザクションをスキップ、gorm は単一の作成と更新のためにトランザクションを開始してデータ整合性を維持
SkipDefaultTransaction bool
// カスタム命名戦略
NamingStrategy schema.Namer
// 完全な関連付けを保存
FullSaveAssociations bool
// カスタムロガー
Logger logger.Interface
// カスタム nowfunc、CreatedAt と UpdatedAt フィールドの注入に使用
NowFunc func() time.Time
// SQL のみ生成して実行しない
DryRun bool
// プリペアドステートメントを使用
PrepareStmt bool
// 接続確立後、データベースを ping
DisableAutomaticPing bool
// データベースマイグレーション時に外部キーを無視
DisableForeignKeyConstraintWhenMigrating bool
// データベースマイグレーション時に関連付け参照を無視
IgnoreRelationshipsWhenMigrating bool
// ネストされたトランザクションを無効化
DisableNestedTransaction bool
// グローバル更新を許可、where 句なしで更新
AllowGlobalUpdate bool
// テーブルのすべてのフィールドをクエリ
QueryFields bool
// バッチ作成サイズ
CreateBatchSize int
// エラー翻訳を有効化
TranslateError bool
// ClauseBuilders 句ビルダー
ClauseBuilders map[string]clause.ClauseBuilder
// ConnPool db 接続プール
ConnPool ConnPool
// Dialector データベースダイアレクト
Dialector
// Plugins 登録されたプラグイン
Plugins map[string]Plugin
callbacks *callbacks
cacheStore *sync.Map
}モデル
gorm では、モデルはデータベーステーブルに対応します。通常は構造体で表されます。例えば以下の構造体です。
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
}構造体の内部フィールドは、基本データ型と sql.Scanner および sql.Valuer インターフェースを実装する型で構成できます。デフォルトでは、Person 構造体によってマップされるテーブル名は persons で、スネークケースの複数形スタイルで、アンダースコアで区切られます。カラム名もスネークケーススタイルで、例えば Id はカラム名 id に対応します。Gorm はこれに対するいくつかの設定方法も提供します。
カラム名の指定
構造体タグを通じて、構造体フィールドのカラム名を指定できます。このようにすると、エンティティマッピング中に gorm は指定されたカラム名を使用します。
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}テーブル名の指定
Table インターフェースを実装することで、テーブル名を指定できます。これは 1 つのメソッドのみを持ち、テーブル名を返します。
type Tabler interface {
TableName() string
}実装されたメソッドで、文字列 person を返します。データベースマイグレーション中に、gorm は person という名前のテーブルを作成します。
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}
func (p Person) TableName() string {
return "person"
}命名戦略については、接続作成時に独自の実装を渡すことで、カスタム効果を実現できます。
時間追跡
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}
func (p Person) TableName() string {
return "person"
}CreatedAt または UpdatedAt フィールドを含む場合、レコード作成または更新時に、ゼロ値の場合、gorm は自動的に time.Now() を使用して時間を設定します。
db.Create(&Person{
Name: "jack",
Address: "usa",
Mom: "lili",
Dad: "tom",
})
// INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom','2023-10-25 14:43:57.16','2023-10-25 14:43:57.16')Gorm はタイムスタンプ追跡もサポートしています
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string
Mom string
Dad string
// ナノ秒
CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
// ミリ秒
UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}その後、Create 実行中に、以下の SQL と同等になります
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)実際の状況では、時間追跡が必要な場合、バックエンドでタイムスタンプを保存することをお勧めします。これはクロスタイムゾーンシナリオで処理が簡単です。
モデル
Gorm は事前定義された Model 構造体を提供しており、ID 主キー、2 つの時間追跡フィールド、およびソフトデリート記録フィールドを含みます。
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}使用するには、エンティティモデルに埋め込むだけです。
type Order struct {
gorm.Model
Name string
}このようにすると、自動的に gorm.Model のすべての機能を持ちます。
主キー
デフォルトでは、Id という名前のフィールドが主キーです。構造体タグを使用して主キーフィールドを指定できます。
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}複数のフィールドが複合主キーを形成します
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}インデックス
index 構造体タグを通じてカラムインデックスを指定できます
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string `gorm:"index:idx_addr,unique,sort:desc;"`
Mom string
Dad string
// ナノ秒
CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
// ミリ秒
UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}上記の構造体では、Address フィールドにユニークインデックスが作成されます。2 つのフィールドが同じインデックス名を使用すると、複合インデックスが作成されます
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string `gorm:"index:idx_addr,unique;"`
School string `gorm:"index:idx_addr,unique;"`
Mom string
Dad string
// ナノ秒
CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
// ミリ秒
UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}外部キー
構造体では、外部キー関係は構造体を埋め込むことで定義されます。例えば
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
MomId uint
Mom Mom `gorm:"foreignKey:MomId;"`
DadId uint
Dad Dad `gorm:"foreignKey:DadId;"`
}
type Mom struct {
Id uint
Name string
Persons []Person `gorm:"foreignKey:MomId;"`
}
type Dad struct {
Id uint
Name string
Persons []Person `gorm:"foreignKey:DadId;"`
}例では、Person 構造体は 2 つの外部キーを持ち、それぞれ Dad と Mom 構造体の主キーを参照します。デフォルトでは、主キーを参照します。Person は Dad と Mom と 1 対 1 の関係を持ちます - 1 人は 1 人の父親と 1 人の母親しか持てません。Dad と Mom は Person と 1 対多の関係を持ちます。父親と母親は複数の子供を持てるからです。
Mom Mom `gorm:"foreignKey:MomId;"`構造体を埋め込む目的は、外部キーと参照を指定しやすくすることです。デフォルトでは、外部キーフィールド名フォーマットは ReferencedTypeName+Id です。例えば MomId です。デフォルトでは、主キーを参照します。参照するフィールドを構造体タグを通じて指定できます
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
MomId uint
Mom Mom `gorm:"foreignKey:MomId;references:Sid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
DadId uint
Dad Dad `gorm:"foreignKey:DadId;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Mom struct {
Id uint
Sid uint `gorm:"uniqueIndex;"`
Name string
Persons []Person `gorm:"foreignKey:MomId;"`
}その中で、constraint:OnUpdate:CASCADE,OnDelete:SET NULL; は外部キー制約を定義します。
フック
エンティティモデルはフックをカスタマイズできます
- Create
- Update
- Delete
- Query
対応するインターフェースは以下の通りです
// 作成前にトリガー
type BeforeCreateInterface interface {
BeforeCreate(*gorm.DB) error
}
// 作成後にトリガー
type AfterCreateInterface interface {
AfterCreate(*gorm.DB) error
}
// 更新前にトリガー
type BeforeUpdateInterface interface {
BeforeUpdate(*gorm.DB) error
}
// 更新後にトリガー
type AfterUpdateInterface interface {
AfterUpdate(*gorm.DB) error
}
// 保存前にトリガー
type BeforeSaveInterface interface {
BeforeSave(*gorm.DB) error
}
// 保存後にトリガー
type AfterSaveInterface interface {
AfterSave(*gorm.DB) error
}
// 削除前にトリガー
type BeforeDeleteInterface interface {
BeforeDelete(*gorm.DB) error
}
// 削除後にトリガー
type AfterDeleteInterface interface {
AfterDelete(*gorm.DB) error
}
// クエリ後にトリガー
type AfterFindInterface interface {
AfterFind(*gorm.DB) error
}構造体はこれらのインターフェースを実装することで動作をカスタマイズできます。
タグ
以下は gorm がサポートするいくつかのタグです
| タグ名 | 説明 |
|---|---|
column | db カラム名を指定 |
type | カラムデータ型、互換性のある汎用型を使用することをお勧めします。例えば、すべてのデータベースは bool、int、uint、float、string、time、bytes をサポートしており、他のタグ(not null、size、autoIncrement など)と併用できます。varbinary(8) のような指定されたデータベースデータ型もサポートされています。指定されたデータベースデータ型を使用する場合、完全なデータベースデータ型である必要があります。例えば MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | データをデータベースにシリアライズ/デシリアライズするシリアライザーを指定。例えば serializer:json/gob/unixtime |
size | カラムデータ型のサイズまたは長さを定義。例えば size: 256 |
primaryKey | カラムを主キーとして定義 |
unique | カラムをユニークキーとして定義 |
default | カラムデフォルト値を定義 |
precision | カラム精度を指定 |
scale | カラムスケールを指定 |
not null | カラムを NOT NULL として指定 |
autoIncrement | カラムを自動増分として指定 |
autoIncrementIncrement | 自動増分ステップ、連続レコード間の間隔を制御 |
embedded | 埋め込みフィールド |
embeddedPrefix | 埋め込みフィールドのカラム名プレフィックス |
autoCreateTime | 作成時に現在時間を追跡。int フィールドの場合、タイムスタンプ秒を追跡。nano/milli を使用してナノ秒/ミリ秒タイムスタンプを追跡可能。例えば autoCreateTime:nano |
autoUpdateTime | 作成/更新時に現在時間を追跡。int フィールドの場合、タイムスタンプ秒を追跡。nano/milli を使用してナノ秒/ミリ秒タイムスタンプを追跡可能。例えば autoUpdateTime:milli |
index | パラメータに基づいてインデックスを作成。複数のフィールドが同じ名前を使用すると複合インデックスが作成されます。詳細は Indexes を参照 |
uniqueIndex | index と同じだがユニークインデックスを作成 |
check | チェック制約を作成。例えば check:age > 13。詳細は Constraints を参照 |
<- | フィールド書き込み権限を設定。<-:create 作成のみ、<-:update 更新のみ、<-:false 書き込み権限なし、<- 作成および更新権限 |
-> | フィールド読み取り権限を設定。->:false 読み取り権限なし |
- | このフィールドを無視。- は読み取り/書き込みなし、-:migration はマイグレーション権限なし、-:all は読み取り/書き込み/マイグレーション権限なし |
comment | マイグレーション中にフィールドにコメントを追加 |
foreignKey | 現在のモデルのカラムを結合テーブルの外部キーとして指定 |
references | 参照されるテーブルのカラム名を指定。結合テーブル外部キーとしてマップされます |
polymorphic | ポリモーフィック型を指定。例えばモデル名 |
polymorphicValue | ポリモーフィック値を指定。デフォルトはテーブル名 |
many2many | 結合テーブル名を指定 |
joinForeignKey | 結合テーブルの外部キーカラム名を指定。現在のテーブルにマップされます |
joinReferences | 結合テーブルの外部キーカラム名を指定。参照されるテーブルにマップされます |
constraint | 関係制約。例えば OnUpdate、OnDelete |
マイグレーション
AutoMigrate メソッドは自動マイグレーションを支援します。テーブル、制約、インデックス、外部キーなどを作成します。
func (db *DB) AutoMigrate(dst ...interface{}) error例えば
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"type:varchar(100);uniqueIndex;"`
Address string
}
type Order struct {
Id uint
Name string
}
db.AutoMigrate(Person{}, Order{})
// CREATE TABLE `person` (`id` bigint unsigned AUTO_INCREMENT,`name` varchar(100),`address` longtext,PRIMARY KEY (`id`),UNIQUE INDEX `idx_person_name` (`name`))
// CREATE TABLE `orders` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`))または Migrator メソッドを通じて Migrator インターフェースにアクセスして手動で操作できます
func (db *DB) Migrator() Migrator以下のインターフェースメソッドをサポートしています
type Migrator interface {
// 自動マイグレーション
AutoMigrate(dst ...interface{}) error
// データベース
CurrentDatabase() string
FullDataTypeOf(*schema.Field) clause.Expr
GetTypeAliases(databaseTypeName string) []string
// テーブル
CreateTable(dst ...interface{}) error
DropTable(dst ...interface{}) error
HasTable(dst interface{}) bool
RenameTable(oldName, newName interface{}) error
GetTables() (tableList []string, err error)
TableType(dst interface{}) (TableType, error)
// カラム
AddColumn(dst interface{}, field string) error
DropColumn(dst interface{}, field string) error
AlterColumn(dst interface{}, field string) error
MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
HasColumn(dst interface{}, field string) bool
RenameColumn(dst interface{}, oldName, field string) error
ColumnTypes(dst interface{}) ([]ColumnType, error)
// ビュー
CreateView(name string, option ViewOption) error
DropView(name string) error
// 制約
CreateConstraint(dst interface{}, name string) error
DropConstraint(dst interface{}, name string) error
HasConstraint(dst interface{}, name string) bool
// インデックス
CreateIndex(dst interface{}, name string) error
DropIndex(dst interface{}, name string) error
HasIndex(dst interface{}, name string) bool
RenameIndex(dst interface{}, oldName, newName string) error
GetIndexes(dst interface{}) ([]Index, error)
}メソッドリストは複数の次元を含みます:データベース、テーブル、カラム、ビュー、インデックス、制約。カスタマイズが必要なユーザーには、より細かい操作を許可します。
テーブルコメントの指定
マイグレーション中にテーブルコメントを追加したい場合は、以下のように設定できます
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})AutoMigrate() メソッドを使用してマイグレーションを行う場合、構造体間に参照関係があると、gorm は再帰的に参照テーブルを先に作成することに注意してください。これにより、参照テーブルと参照するテーブルの両方に重複したコメントが付与されます。そのため、CreateTable メソッドを使用して作成することをお勧めします。
TIP
テーブル作成時、CreateTable メソッドは参照するテーブルが参照するテーブルの前に作成されている必要があります。否则会报错。AutoMigrate メソッドはこれを必要としません。参照関係に従ってテーブルを再帰的に作成するためです。
作成
作成
新しいレコードを作成する場合、ほとんどの場合 Create メソッドが使用されます
func (db *DB) Create(value interface{}) (tx *DB)以下の構造体が与えられた場合
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}レコードを作成
user := Person{
Name: "jack",
}
// 参照を渡す必要があります
db = db.Create(&user)
// 実行中にエラーが発生
err = db.Error
// 影響を受けた行数
affected := db.RowsAffected作成後、gorm は主キーを user 構造体に書き込みます。これがポインタを渡す必要がある理由です。スライスを渡すと、バッチ作成されます
user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Create(&user)同様に、gorm は主キーをスライスに書き込みます。データ量が大きすぎる場合、CreateInBatches メソッドを使用してバッチで作成することもできます。生成された INSERT INTO table VALUES (),() SQL 文は非常に長くなり、各データベースには SQL 長の制限があるためです。必要に応じて、バッチで作成することを選択できます。
db = db.CreateInBatches(&user, 50)さらに、Save メソッドもレコードを作成できます。その機能は、主キーが一致する場合にレコードを更新し、そうでない場合に挿入することです。
func (db *DB) Save(value interface{}) (tx *DB)user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Save(&user)Upsert
Save メソッドは主キーのみをマッチできます。Clause を構築することで、よりカスタムな upsert を構築できます。例えば、この行のコード
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: false,
DoUpdates: clause.AssignmentColumns([]string{"address"}),
UpdateAll: false,
}).Create(&p)その機能は:name フィールドが競合する場合、address フィールドの値を更新します。競合がない場合、新しいレコードを作成します。競合がある場合に何もしないこともできます
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)またはすべてのフィールドを直接更新
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
UpdateAll: true,
}).Create(&p)upsert を使用する前に、競合フィールドにインデックスを追加することを忘れないでください。
クエリ
First
クエリについて、gorm は多くのメソッドを提供しています。最初は First メソッドです
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)その機能は、主キー昇順で最初のレコードを検索することです。例えば
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffecteddest ポインタを渡して、gorm がクエリされたデータを構造体にマップできるようにします。
または Table および Model メソッドを使用してクエリテーブルを指定します。前者は文字列テーブル名を受け取り、後者はエンティティモデルを受け取ります。
db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)TIP
渡されたポインタ要素がエンティティモデル(構造体ポインタや構造体スライスポインタなど)を含む場合、クエリするテーブルを手動で指定する必要はありません。このルールはすべての CRUD 操作に適用されます。
Take
Take メソッドは First と似ていますが、主キーでソートしない点が異なります。
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffectedPluck
Pluck メソッドは、テーブルから単一カラムをバッチクエリするために使用されます。クエリ結果は、エンティティ型スライスに限定されない、指定された型のスライスに収集できます。
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)例えば、すべての人の住所を文字列スライスに収集
var adds []string
// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)実際には以下と同等です
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)Count
Count メソッドはエンティティレコードの数をカウントするために使用されます
func (db *DB) Count(count *int64) (tx *DB)使用例を参照
var count int64
// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)Find
バッチクエリに最も一般的に使用されるメソッドは Find メソッドです
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)与えられた条件に一致するすべてのレコードを検索します
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)Select
デフォルトでは、gorm はすべてのフィールドをクエリします。Select メソッドを通じてフィールドを指定できます
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)例えば
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)以下と同等
db.Select([]string{"address", "name"}).First(&p)同時に、Omit メソッドを使用してフィールドを無視できます
func (db *DB) Omit(columns ...string) (tx *DB)例えば
// SELECT `person`.`id`,`person`.`name` FROM `person` WHERE id IN (1,2,3,4)
db.Omit("address").Where("id IN ?", []int{1, 2, 3, 4}).Find(&ps)Select および Omit によって選択または無視されたフィールドは、作成および更新クエリ時にも有効になります。
Where
条件クエリは Where メソッドを使用します
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)簡単な例
var p Person
db.Where("id = ?", 1).First(&p)チェーン操作で複数の Where を使用すると、複数の AND 文が構築されます。例えば
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)または Or メソッドを使用して OR 文を構築
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
Or("name = ?", "jack").
Where("address = ?", "usa").
First(&p)同様の Not メソッドもあります
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND NOT name = 'mike' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
Or("name = ?", "jack").
Not("name = ?", "mike").
Where("address = ?", "usa").
First(&p)IN 条件の場合、スライスを直接 Where メソッドに渡せます。
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)または複数カラムの IN 条件の場合、[][]any 型を使用してパラメータを運ぶ必要があります
// SELECT * FROM `person` WHERE (id, name, address) IN ((1,'jack','uk'),(2,'mike','usa'))
db.Where("(id, name, address) IN ?", [][]any{{1, "jack", "uk"}, {2, "mike", "usa"}}).Find(&ps)Gorm は where グルーピングをサポートしており、上記の文を結合します
db.Where(
db.Where("name IN ?", []string{"cn", "uk"}).Where("id IN ?", []uint{1, 2}),
).Or(
db.Where("name IN ?", []string{"usa", "jp"}).Where("id IN ?", []uint{3, 4}),
).Find(&ps)
// SELECT * FROM `person` WHERE (name IN ('cn','uk') AND id IN (1,2)) OR (name IN ('usa','jp') AND id IN (3,4))Order
ソートは Order メソッドを使用します
func (db *DB) Order(value interface{}) (tx *DB)使用例を参照
var ps []Person
// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)複数回呼び出すこともできます
// SELECT * FROM `person` ORDER BY name ASC, id DESC, address
db.Order("name ASC, id DESC").Order("address").Find(&ps)Limit
Limit および Offset メソッドはページネーションクエリに一般的に使用されます
func (db *DB) Limit(limit int) (tx *DB)
func (db *DB) Offset(offset int) (tx *DB)簡単なページネーション例
var (
ps []Person
page = 2
size = 10
)
// SELECT * FROM `person` LIMIT 10 OFFSET 10
db.Offset((page - 1) * size).Limit(size).Find(&ps)Group
Group および Having メソッドは主にグルーピング操作に使用されます
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)例を参照
var (
ps []Person
)
// SELECT `address` FROM `person` GROUP BY `address` HAVING address IN ('cn','us')
db.Select("address").Group("address").Having("address IN ?", []string{"cn", "us"}).Find(&ps)Distinct
Distinct メソッドは主に重複除去に使用されます
func (db *DB) Distinct(args ...interface{}) (tx *DB)例を参照
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)SubQuery
サブクエリはネストされたクエリです。例えば、id 値が平均値より大きいすべての人を検索したい場合
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)From サブクエリ
// SELECT * FROM (SELECT * FROM `person` WHERE address IN ('cn','uk')) as p
db.Table("(?) as p", db.Model(Person{}).Where("address IN ?", []string{"cn", "uk"})).Find(&ps)Lock
Gorm は clause.Locking 句を使用してロックサポートを提供します
// SELECT * FROM `person` FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&ps)
// SELECT * FROM `person` FOR SHARE NOWAIT
db.Clauses(clause.Locking{Strength: "SHARE", Options: "NOWAIT"}).Find(&ps)Iteration
Rows メソッドを通じてイテレーターを取得できます
func (db *DB) Rows() (*sql.Rows, error)イテレーターをトラバースし、ScanRows メソッドを使用して各行の 결과를 構造体にスキャンします。
rows, err := db.Model(Person{}).Rows()
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var p Person
err := db.ScanRows(rows, &p)
if err != nil {
return
}
}更新
Save
Save メソッドは作成中に言及しました。レコードの更新にも使用でき、すべてのフィールドを更新します。一部の構造体フィールドがゼロ値であっても。ただし、主キーが一致しない場合、挿入操作を実行します。
var p Person
db.First(&p)
p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)主キーを除くすべてのフィールドを SET 文に追加することがわかります。
Update
そのため、ほとんどの場合、Update メソッドを使用することをお勧めします
func (db *DB) Update(column string, value interface{}) (tx *DB)主に単一カラムフィールドを更新するために使用されます
var p Person
db.First(&p)
// UPDATE `person` SET `address`='poland' WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Update("address", "poland")Updates
Updates メソッドは複数のカラムを更新するために使用され、構造体とマップをパラメータとして受け取ります。構造体フィールドがゼロ値の場合、それらのフィールドは無視されますが、マップでは無視されません。
func (db *DB) Updates(values interface{}) (tx *DB)例
var p Person
db.First(&p)
// UPDATE `person` SET `name`='jojo',`address`='poland' WHERE `id` = 2
db.Model(p).Updates(Person{Name: "jojo", Address: "poland"})
// UPDATE `person` SET `address`='poland',`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "address": "poland"})SQL 式
場合によっては、フィールドで自己増分または自己減分、またはフィールド自体を含む他の操作を実行する必要があることがよくあります。一般的には、まずクエリし、次に計算し、次に更新するか、SQL 式を使用します。
func Expr(expr string, args ...interface{}) clause.Expr以下の例を参照
// UPDATE `person` SET `age`=age + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age + age")})
// UPDATE `person` SET `age`=age * 2 + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age * 2 + age")})削除
gorm では、レコードの削除は Delete メソッドを使用します。エンティティ構造体を直接渡すか、条件を渡すことができます。
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)例えば、構造体を直接渡す
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)または
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)または条件を指定
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)または以下のように簡略化
var p Person
db.First(&p)
// DELETE FROM `person` WHERE id = 2
db.Delete(&Person{}, "id = ?", 2)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&Person{}, 2)バッチ削除の場合、スライスを渡す
// DELETE FROM `person` WHERE id IN (1,2,3)
db.Delete(&Person{}, "id IN ?", []uint{1, 2, 3})
// DELETE FROM `person` WHERE `person`.`id` IN (1,2,3)
db.Delete(&Person{}, []uint{1, 2, 3})ソフトデリート
エンティティモデルがソフトデリートを使用している場合、削除時にはデフォルトで更新操作を実行します。完全に削除したい場合は、Unscoped メソッドを使用できます
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})関連付け定義
Gorm はテーブル関連付け操作機能を提供し、埋め込まれた構造体とフィールドを通じて構造体間の関連付けを定義します。
1 対 1
1 対 1 の関係は最もシンプルです。通常、1 人は 1 人の母親しか持てません。以下の構造体を参照
type Person struct {
Id uint
Name string
Address string
Age uint
MomId sql.NullInt64
Mom Mom `gorm:"foreignKey:MomId;"`
}
type Mom struct {
Id uint
Name string
}Person 構造体は Mom 構造体を埋め込むことで Mom 型への参照を実装します。その中で、Person.MomId は参照フィールドで、主キー Mom.Id は参照されるフィールドです。これで 1 対 1 の関連付けが完了します。外部キー、参照、制約をカスタマイズする方法、およびデフォルトの外部キールールは 外部キー定義 で説明済みなので、ここでは繰り返しません。
TIP
外部キーフィールドについては、sql パッケージによって提供される型を使用することをお勧めします。外部キーはデフォルトで NULL になる可能性があるためです。Create を使用してレコードを作成する際、通常の型を使用すると、ゼロ値 0 も作成されます。存在しない外部キーを作成することは明らかに許可されていません。
1 対多
以下に、学校構造体を追加します。学校と学生の関係は 1 対多です。学校は複数の学生を持ちますが、学生は 1 つの学校にしか通えません。
type Person struct {
Id uint
Name string
Address string
Age uint
MomId sql.NullInt64
Mom Mom `gorm:"foreignKey:MomId;"`
SchoolId sql.NullInt64
School School `gorm:"foreignKey:SchoolId;"`
}
type Mom struct {
Id uint
Name string
}
type School struct {
Id uint
Name string
Persons []Person `gorm:"foreignKey:SchoolId;"`
}school.Personsは[]person型で、複数の学生を持つことができることを示し、PersonはSchoolを参照する外部キー、つまりPerson.SchoolIdを含む必要があります。
多対多
一人が多くの家を持つことができ、一つの家に多くの人が住むことができるという関係は、多対多の関係です。
type Person struct {
Id uint
Name string
Address string
Age uint
MomId sql.NullInt64
Mom Mom `gorm:"foreignKey:MomId;"`
SchoolId sql.NullInt64
School School `gorm:"foreignKey:SchoolId;"`
Houses []House `gorm:"many2many:person_house;"`
}
type Mom struct {
Id uint
Name string
}
type School struct {
Id uint
Name string
Persons []Person
}
type House struct {
Id uint
Name string
Persons []Person `gorm:"many2many:person_house;"`
}
type PersonHouse struct {
PersonId sql.NullInt64
Person Person `gorm:"foreignKey:PersonId;"`
HouseId sql.NullInt64
House House `gorm:"foreignKey:HouseId;"`
}PersonとHouseは互いに持つ对方のスライス型を持つことで、多対多の関係を示します。多対多の関係は、通常、接続テーブルを作成する必要があります。many2manyを使用して接続テーブルを指定し、接続テーブルの外部キーを正しく指定する必要があります。
完了した構造体を gorm に自動マイグレートするには、次のようにします。
tables := []any{
School{},
Mom{},
Person{},
House{},
PersonHouse{},
}
for _, table := range tables {
db.Migrator().CreateTable(&table)
}注意:引用表と被引用表の作成順序を正しく指定する必要があります。
関連付け操作
上記の 3 つの関連付け関係を作成した後、次は関連付けを使用して増減改査を行う方法を説明します。主要にAssociationメソッドを使用します。
func (db *DB) Association(column string) *Associationこのメソッドは、関連付けパラメータを受け取ります。このパラメータの値は、埋め込まれた参照構造体内の被参照型のフィールド名である必要があります。
db.Model(&person).Association("Mom").Find(&mom)例えば、一人の人の母親を探すには、AssociationのパラメータはMomで、つまりPerson.Momフィールド名です。
関連付けの作成
// 定義データ
jenny := Mom{
Name: "jenny",
}
mit := School{
Name: "MIT",
Persons: nil,
}
h1 := House{
Id: 0,
Name: "h1",
Persons: nil,
}
h2 := House{
Name: "h2",
Persons: nil,
}
jack := Person{
Name: "jack",
Address: "usa",
Age: 18,
}
mike := Person{
Name: "mike",
Address: "uk",
Age: 20,
}
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('jack','usa',18,NULL,NULL)
db.Create(&jack)
// INSERT INTO `schools` (`name`) VALUES ('MIT')
db.Create(&mit)
// PersonとMomの関連付けを追加、一対一の関連付け
// INSERT INTO `moms` (`name`) VALUES ('jenny') ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=1 WHERE `id` = 1
db.Model(&jack).Association("Mom").Append(&jenny)
// PersonとSchoolの関連付けを追加、一対多の関連付け
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('jack','usa',18,1,1,1),('mike','uk',20,NULL,1,DEFAULT) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
db.Model(&mit).Association("Persons").Append([]Person{jack, mike})
// PersonとHouseの関連付けを追加、多対多の関連付け
// INSERT INTO `houses` (`name`) VALUES ('h1'),('h2') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,1),(1,2) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
db.Model(&jack).Association("Houses").Append([]House{h1, h2})例えば、一人の人が二つの家に住むことができるという関係は、多対多の関係です。多対多の関係を作成するには、接続テーブルを作成する必要があります。many2manyを使用して接続テーブルを指定し、接続テーブルの外部キーを正しく指定する必要があります。
假如所有的记录都不存在,在进行关联创建时,也会先创建记录再创建关联。
関連付けの検索
下面演示如何进行関連付けの検索。
// 一対一の関連付け検索
var person Person
var mom Mom
// SELECT * FROM `people` ORDER BY `people`.`id` LIMIT 1
db.First(&person)
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
db.Model(person).Association("Mom").Find(&mom)
// 一対多の関連付け検索
var school School
var persons []Person
// SELECT * FROM `schools` ORDER BY `schools`.`id` LIMIT 1
db.First(&school)
// SELECT * FROM `people` WHERE `people`.`school_id` = 1
db.Model(&school).Association("Persons").Find(&persons)
// 多対多の関連付け検索
var houses []House
// SELECT `houses`.`id`,`houses`.`name` FROM `houses` JOIN `person_house` ON `person_house`.`house_id` = `houses`.`id` AND `person_house`.`person_id` IN (1,2)
db.Model(&persons).Association("Houses").Find(&houses)関連付けの検索は、既存のデータを基に参照表から一致するレコードを検索します。多対多の関係の場合、gorm は自動的に接続テーブルを結合して関連付けを検索します。
関連付けの更新
下面演示如何进行関連付けの更新
// 一対一の関連付け更新
var jack Person
lili := Mom{
Name: "lili",
}
// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)
// INSERT INTO `moms` (`name`) VALUES ('lili')
db.Create(&lili)
// INSERT INTO `moms` (`name`,`id`) VALUES ('lili',2) ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=2 WHERE `id` = 1
db.Model(&jack).Association("Mom").Replace(&lili)
// 一对多关联更新
var mit School
newPerson := []Person{{Name: "bob"}, {Name: "jojo"}}
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('bob','',0,NULL,NULL),('jojo','',0,NULL,NULL)
db.Create(&newPerson)
// SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('bob','',0,NULL,1,4),('jojo','',0,NULL,1,5) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
// UPDATE `people` SET `school_id`=NULL WHERE `people`.`id` NOT IN (4,5) AND `people`.`school_id` = 1
db.Model(&mit).Association("Persons").Replace(newPerson)
// 多对多关联更新
// INSERT INTO `houses` (`name`) VALUES ('h3'),('h4'),('h5') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,3),(1,4),(1,5) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` NOT IN (3,4,5)
db.Model(&jack).Association("Houses").Replace([]House{{Name: "h3"}, {Name: "h4"}, {Name: "h5"}})関連付けの更新時に、被参照データと参照データが両方存在しない場合、gorm は自動的にそれらを作成します。
関連付けの削除
下面演示如何删除関連付け
// 一対一の関連付け削除
var (
jack Person
lili Mom
)
// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)
// SELECT * FROM `moms` WHERE name = 'lili' ORDER BY `moms`.`id` LIMIT 1
db.Where("name = ?", "lili").First(&lili)
// UPDATE `people` SET `mom_id`=NULL WHERE `people`.`id` = 1 AND `people`.`mom_id` = 2
db.Model(&jack).Association("Mom").Delete(&lili)
// 一対多の関連付け削除
var (
mit School
persons []Person
)
// SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)
// SELECT * FROM `people` WHERE name IN ('jack','mike')
db.Where("name IN ?", []string{"jack", "mike"}).Find(&persons)
// UPDATE `people` SET `school_id`=NULL WHERE `people`.`school_id` = 1 AND `people`.`id` IN (1,2)
db.Model(&mit).Association("Persons").Delete(&persons)
// 多対多の関連付け削除
var houses []House
// SELECT * FROM `houses` WHERE name IN ('h3','h4')
db.Where("name IN ?", []string{"h3", "h4"}).Find(&houses)
// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` IN (3,4)
db.Model(&jack).Association("Houses").Delete(&houses)関連付けの削除時には、参照関係のみが削除され、エンティティ自体は削除されません。Clearメソッドを使用すると、関連付けを直接クリアできます。
db.Model(&jack).Association("Houses").Clear()もし、関連付けを削除する際に、エンティティ自体も削除したい場合は、Unscoped操作を追加します(多対多の関係には影響しません)。
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)一対多と多対多の関連付けを削除する際には、Select操作を使用して、関連付けを直接削除できます。
var (
mit School
)
db.Where("name = ?", "mit").First(&mit)
db.Select("Persons").Delete(&mit)関連付けの预加载
プリロードは関連データを照会するために使用され、関連関係を持つエンティティに対しては、まず関連参照先のエンティティを事前にロードします。先に述べた関連照会は関連関係自体を照会するもので、プリロードはエンティティレコードを直接照会し、すべての関連関係を含めて取得します。構文的には、関連照会はまず指定された[]Personを取得し、その後で[]Personに基づいて関連する[]Momを照会しますが、プリロードは構文的に直接[]Personを照会し、すべての関連関係もまとめてロードします。ただし実際に実行されるSQLはほぼ同じです。以下に例を示します
var users []Person
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)これは、一対一の関連照会の例です。出力結果には、Mom が埋め込まれています。
[{Id:1 Name:jack Address:usa Age:18 MomId:{Int64:1 Valid:true} Mom:{Id:1 Name:jenny} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]} {Id:2 Name:mike Address:uk Age:20 MomId:{Int64:0 Valid:false} Mom:{Id:0 Name:} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]}]関連するMomも一緒に取得されていることがわかりますが、学校のリレーションはプリロードされておらず、すべてのSchool構造体はゼロ値になっています。また、clause.Associationsを使用すると、ネストされたリレーションを除くすべてのリレーションをプリロードすることもできます。
db.Preload(clause.Associations).Find(&users)以下は、ネストされたプリロードの例です。これは、学校が所有するすべての学生、それぞれの学生が所有する母亲、それぞれの学生が所有する家、それぞれの家の所有する学生を照会します。つまり、学校->学生->母親->家->学生の順番でプリロードされます。
var schools []School
db.Preload("Persons").
Preload("Persons.Mom").
Preload("Persons.Houses").
Preload("Persons.Houses.Persons").Find(&schools)
// 输出代码,逻辑可以忽略
for _, school := range schools {
fmt.Println("school", school.Name)
for _, person := range school.Persons {
fmt.Println("person", person.Name)
fmt.Println("mom", person.Mom.Name)
for _, house := range person.Houses {
var persons []string
for _, p := range house.Persons {
persons = append(persons, p.Name)
}
fmt.Println("house", house.Name, "owner", persons)
}
fmt.Println()
}
}出力結果は以下の通りです。
school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]
person mike
momこれにより、学校が所有するすべての学生、それぞれの学生が所有する母親、それぞれの学生が所有する家、それぞれの家の所有する学生が出力されます。
トランザクション
gorm はデフォルトでトランザクションを有効にしています。挿入や更新操作が失敗した場合、自動的にロールバックされます。接続設定でトランザクションを無効にすることもできますが、性能は約 30% 程度改善します。gorm では、トランザクションの使用方法はいくつかあります。以下に簡単に紹介します。
自動
クロージャートランザクションは、Transactionメソッドを使用して、クロージャー関数を渡します。関数がエラーを返す場合、トランザクションは自動的にロールバックされます。
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)以下は、クロージャートランザクションの例です。クロージャー内の操作は、パラメータtxを使用して、外部のdbとは独立して実行されます。関数がエラーを返す場合、トランザクションは自動的にロールバックされます。
var ps []Person
db.Transaction(func(tx *gorm.DB) error {
err := tx.Create(&ps).Error
if err != nil {
return err
}
err = tx.Create(&ps).Error
if err != nil {
return err
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
return err
}
return nil
})手動
手動トランザクションを使用すると、トランザクションを開始した後、自己でロールバックまたはコミットを制御できます。手動トランザクションを使用する場合は、以下の 3 つのメソッドを使用します。
// Beginメソッドは、トランザクションを開始します。
func (db *DB) Begin(opts ...*sql.TxOptions) *DB
// Rollbackメソッドは、トランザクションをロールバックします。
func (db *DB) Rollback() *DB
// Commitメソッドは、トランザクションをコミットします。
func (db *DB) Commit() *DB以下は、手動トランザクションの例です。トランザクションを開始した後、txを使用して操作を行います。操作が成功した場合は、Commitメソッドを呼び出してトランザクションをコミットします。操作が失敗した場合は、Rollbackメソッドを呼び出してトランザクションをロールバックします。
var ps []Person
tx := db.Begin()
err := tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
tx.Rollback()
return
}
tx.Commit()ロールバックポイントを指定できます
var ps []Person
tx := db.Begin()
err := tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
tx.SavePoint("createBatch")
err = tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
tx.RollbackTo("createBatch")
return
}
tx.Commit()まとめ
以上で、gorm を使用してデータベースを操作する方法を紹介しました。gorm は、簡単に使うことができ、多くの機能を提供しています。詳細は、公式ドキュメントを参照してください。
