【Go】GORM v1.30 ジェネリクスAPIで型安全にレコードを更新する方法
はじめに
前回は、GORM v1.30で追加されたジェネリクスAPIで型安全にレコードを抽出する方法について見ていきました。
今回は、同じくジェネリクスAPIを使って、型安全にレコードを更新する方法を見ていきます!
この記事でわかること
- 単一カラムの更新方法
- 複数カラムの更新方法
ジェネリクスAPIを使ったレコードの更新
ここでは、ジェネリクスAPIを使ったレコードの更新方法を、従来の実装と比較しながら見ていきましょう。
1. 単一カラムの更新
従来の単一カラムの更新は、以下のように実装することができます。
// 更新に関わる部分のみ抽出
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Birthday time.Time `gorm:"type:date"`
UpdatedAt time.Time `gorm:"type:date"`
}
birthday, _ := time.Parse(time.DateOnly, "2025-09-03")
user := User{ID: 1, Name: "Tomoya", Birthday: birthday}
// 1. 構造体リテラルをModelメソッドに渡し、条件を設定
db.Model(&User{}).Debug().Where("id = ?", 1).Update("name", "tomoya")
// 2. 構造体インスタンスをModelメソッドに渡す
db.Model(&user).Debug().Update("name", "tomoya")
// 3. 構造体インスタンスをModelメソッドに渡し、条件を設定
db.Model(&user).Debug().Where("birthday = ?", birthday).Update("name", "tomoya")
実行されるSQLは以下のようになります。
# 1. 構造体リテラルをModelメソッドに渡し、条件を設定した場合
[1.760ms] [rows:1] UPDATE "users" SET "name"='tomoya',"updated_at"='2025-09-10 23:37:54.584' WHERE id = 1
# 2. 構造体インスタンスをModelメソッドに渡す場合
[1.023ms] [rows:1] UPDATE "users" SET "name"='tomoya',"updated_at"='2025-09-10 23:37:54.586' WHERE "id" = 1
# 3. 構造体インスタンスをModelメソッドに渡し、条件を設定した場合
[1.107ms] [rows:1] UPDATE "users" SET "name"='tomoya',"updated_at"='2025-09-10 23:37:54.587' WHERE birthday = '2025-09-03 00:00:00' AND "id" = 1
UpdateメソッドはWHERE句を使って絞り込みをしないとエラーが発生します。
Global Updatesを防ぐためです。
一方、ジェネリクスAPIを用いた単一カラムの更新は、以下のように実装することができます。
// 更新に関わる部分のみ抽出
ctx := context.Background()
birthday, _ := time.Parse(time.DateOnly, "2025-09-03")
user := User{ID: 1, Name: "Tomoya", Birthday: birthday}
// 1. 条件を一つ設定
row, err := gorm.G[User](db.Debug()).Where("id = ?", user.ID).Update(ctx, "name", "Tomoya")
if err != nil {
log.Fatal(err)
}
fmt.Println("rowsAffected:", row) // 出力結果: rowsAffected: 1
// 2. 条件を複数設定
row, err := gorm.G[User](db.Debug()).Where("id = ? AND birthday = ?", user.ID, user.Birthday).Update(ctx, "name", "Tomoya")
if err != nil {
log.Fatal(err)
}
fmt.Println("rowsAffected:", row) // 出力結果: rowsAffected: 1
実行されるSQLは以下のようになります。
# 1. 条件を一つ設定
[3.699ms] [rows:1] UPDATE "users" SET "name"='Tomoya',"updated_at"='2025-09-10 23:56:27.266' WHERE id = 1
# 2. 条件を複数設定
[1.514ms] [rows:1] UPDATE "users" SET "name"='Tomoya',"updated_at"='2025-09-10 23:56:27.27' WHERE id = 1 AND birthday = '2025-09-03 00:00:00'
ジェネリクスAPIのUpdateメソッドのシグネチャは以下の通りです。
func (c chainG[T]) Update(ctx context.Context, name string, value any) (rowsAffected int, err error)
Updateメソッドは、指定したカラムに新しい値を設定し、更新を実行するメソッドです。
- 引数:
-
ctx: コンテキスト、処理のキャンセルやタイムアウトを制御するために使用 -
name: 更新対象のカラム名を文字列で指定 -
value: 設定する新しい値を任意の型で指定可能
戻り値:
-
rowsAffected: 更新された行数、0の場合条件に合致するレコードが存在しないことを示す -
err: 更新処理中に発生したエラー、正常に更新できた場合はnil
さて、従来の更新ではSaveメソッドを使うことができました。
このメソッドは、
- 主キーが存在しない場合はCreateを行う
- 主キーが存在する場合はUpdateを行う
というUpsertを行うものでした。
しかし、ジェネリクスAPIではSaveメソッドが廃止となりました。
公式ドキュメントには「曖昧さと並行処理の問題」を防ぐと記載しています。
ですので、ジェネリクスAPIを使って更新する場合はUpdateメソッドを使いましょう。
2. 複数カラムの更新
従来の複数カラムの更新は、以下のように実装することができます。
// 更新に関わる部分のみ抽出
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
IsAdmin bool `gorm:"not null"`
Birthday time.Time `gorm:"type:date"`
UpdatedAt time.Time `gorm:"type:date"`
}
// 1. 構造体を指定した場合
db.Model(&user).Updates(User{Name: "tomoya", IsAdmin: false})
// 2. mapを指定した場合
db.Model(&user).Updates(map[string]interface{}{"name": "tomoya", "is_admin": false})
実行されるSQLは以下のようになります。
# 1. 構造体を指定した場合
[1.530ms] [rows:1] UPDATE "users" SET "name"='tomoya',"updated_at"='2025-09-11 22:01:20.111' WHERE "id" = 1
# 2. mapを指定した場合
[0.889ms] [rows:1] UPDATE "users" SET "is_admin"=false,"name"='tomoya',"updated_at"='2025-09-11 22:01:20.113' WHERE "id" = 1
Updatesメソッドでは、引数に構造体を指定する方法と、mapを指定する方法があります。
両者の最大の違いは、ゼロ値で更新できるかどうかです。
- 構造体を指定した場合→ゼロ値で更新できない
- mapを指定した場合→ゼロ値で更新できる
ただ、構造体を指定した場合はSelectメソッドを使って更新するカラムを指定することで、ゼロ値でも更新することができます。
また、Omitメソッドを使うことで、更新しないカラムを指定することも可能です。
一方、ジェネリクスAPIを用いた複数カラムの更新は、以下のように実装することができます。
// 更新に関わる部分だけ抽出
ctx := context.Background()
row, err := gorm.G[User](db.Debug()).Where("id = ?", user.ID).Updates(ctx, User{Name: "Tomoya", IsAdmin: false})
if err != nil {
log.Fatal(err)
}
fmt.Println("rowsAffected:", row) // 出力結果: rowsAffected: 1
実行されるSQLは以下のようになります。
[2.336ms] [rows:1] UPDATE "users" SET "name"='Tomoya',"updated_at"='2025-09-11 22:12:00.3' WHERE id = 1
ジェネリクスAPIのUpdatesメソッドのシグネチャは以下の通りです。
func (c chainG[T]) Updates(ctx context.Context, t T) (rowsAffected int, err error)
Updatesメソッドは、指定したカラムに新しい値を設定し、更新を実行するメソッドです。
- 引数:
-
ctx: コンテキスト、処理のキャンセルやタイムアウトを制御するために使用 -
t: 型パラメータで、指定された構造体の値を更新対象とする
戻り値:
-
rowsAffected: 更新された行数、0の場合条件に合致するレコードが存在しないことを示す -
err: 更新処理中に発生したエラー、正常に更新できた場合はnil
UpdatesメソッドもUpdateメソッドと同じく、ゼロ値での更新はできません。
更新する必要があれば、Selectメソッドを使ってカラムを指定しましょう。
また、Omitメソッドを使うことで、更新しないカラムを指定することも可能です。
また、従来のUpdatesメソッドではmapを使った更新ができましたが、ジェネリクスAPIでは出来ないようです。
まとめ
今回は、GORM v1.30で追加されたジェネリクスAPIを使って、型安全にレコードを更新する方法を見ていきました。
- 単一カラムの更新:
Updateメソッド- Saveメソッドは廃止になったので注意
- 複数カラムの更新:
Updatesメソッド- ゼロ値での更新ができないので注意
- 必要に応じて
SelectメソッドやOmitメソッドを使う
次回は、「ジェネリクスAPIを使って型安全にレコードを削除する方法」について見ていきます!
Discussion