🏃

【Go】GORM v1.30 ジェネリクスAPIで型安全にレコードを抽出する方法

に公開

はじめに

前回は、GORM v1.30で追加されたジェネリクスAPIで型安全にレコードを登録する方法について見ていきました。

今回は、同じくジェネリクスAPIを使って、型安全にレコードを抽出する方法を見ていきます!

この記事でわかること

  • 単一レコードを抽出する方法
  • 複数レコードを抽出する方法
  • 特定のカラムを抽出する方法

ジェネリクスAPIを用いたレコードの抽出方法

ここでは、ジェネリクスAPIを使ったレコードの抽出方法を、従来の実装と比較しながら見ていきましょう。

1. 単一レコードの抽出

従来の単一レコード抽出は、以下のように実装することができます。

main.go
// 抽出に関わる部分のみ抜粋
type User struct {
    ID       uint      `gorm:"primaryKey"`
    Name     string    `gorm:"not null"`
    Birthday time.Time `gorm:"type:date"`
}

users := User{}
// 主キーの昇順で最初の1件を取得
db.First(&users)

// 条件に一致したレコードを1件だけ取得(順序は保証されない)
db.Take(&users)

// 主キーの降順で最初の1件を取得
db.Last(&users)

実行されるSQLは以下のようになります。

ターミナル
# Firstメソッドの場合
[0.554ms] [rows:1] SELECT * FROM "users" ORDER BY "users"."id" LIMIT 1

# Takeメソッドの場合
[0.402ms] [rows:1] SELECT * FROM "users" LIMIT 1

# Lastメソッドの場合
[0.642ms] [rows:1] SELECT * FROM "users" ORDER BY "users"."id" DESC LIMIT 1

Firstメソッド:主キーの昇順で最初の1件を抽出

finisher_api.go
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)

Takeメソッド:条件に一致したレコードを1件抽出(順序は保証しない)

finisher_api.go
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)

Lastメソッド:主キーの降順で最初の1件を抽出

finisher_api.go
func (db *DB) Last(dest interface{}, conds ...interface{}) (tx *DB)

一方、ジェネリクスAPIを用いた単一レコードの抽出は、以下のように実装することができます。

main.go
// 抽出に関わる部分のみ抜粋
ctx := context.Background()
// 主キーの昇順で最初の1件を取得
user, err := gorm.G[User](db).First(ctx)
if err != nil {
    log.Fatal(err)
}

// 条件に一致したレコードを1件だけ取得(順序は保証されない)
user, err := gorm.G[User](db).Take(ctx)
if err != nil {
    log.Fatal(err)
}

// 主キーの降順で最初の1件を取得
user, err := gorm.G[User](db).Last(ctx)
if err != nil {
    log.Fatal(err)
}

実行されるSQLは従来の場合と同じです。

ターミナル
# Firstメソッドの場合
[0.516ms] [rows:1] SELECT * FROM "users" ORDER BY "users"."id" LIMIT 1

# Takeメソッドの場合
[0.407ms] [rows:1] SELECT * FROM "users" LIMIT 1

# Lastメソッドの場合
[0.397ms] [rows:1] SELECT * FROM "users" ORDER BY "users"."id" DESC LIMIT 1

Firstメソッド:主キーの昇順で最初の1件を抽出

generics.go
func (g execG[T]) First(ctx context.Context) (T, error)

Takeメソッド:条件に一致したレコードを1件抽出(順序は保証しない)

generics.go
func (g execG[T]) Take(ctx context.Context) (T, error)

Lastメソッド:主キーの降順で最初の1件を抽出

generics.go
func (g execG[T]) Last(ctx context.Context) (T, error)

2. 複数レコードの抽出

従来の複数レコード抽出は、以下のように実装することができます。

main.go
// 抽出に関わる部分のみ抜粋
type User struct {
    ID       uint      `gorm:"primaryKey"`
    Name     string    `gorm:"not null"`
    Birthday time.Time `gorm:"type:date"`
}
users := []User{}
// 全レコードを抽出
db.Find(&users)

実行されるSQLは以下のようになります。

ターミナル
# Findメソッド
[0.652ms] [rows:12] SELECT * FROM "users"

Findメソッド:複数レコードを抽出

finisher_api.go
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)

一方、ジェネリクスAPIを用いた複数レコードの抽出は、以下のように実装することができます。

main.go
// 抽出に関わる部分のみ抜粋
ctx := context.Background()
users, err := gorm.G[User](db).Find(ctx)
if err != nil {
    log.Fatal(err)
}

実行されるSQLは従来の場合と同じです。

ターミナル
# Findメソッド
[0.805ms] [rows:12] SELECT * FROM "users"

Findメソッド:複数レコードを抽出

generics.go
func (g execG[T]) Find(ctx context.Context) ([]T, error)

3. 特定カラムの抽出

従来の特定カラムの抽出は、以下のように実装することができます。

main.go
type User struct {
	ID       uint      `gorm:"primaryKey"`
	Name     string    `gorm:"not null"`
	Birthday time.Time `gorm:"type:date"`
}
type UserName struct {
    Name string
}
userNames := []UserName{}
db.Model(&User{}).Select("name").Scan(&userNames)
実行されるSQLは以下のようになります。
```bash:ターミナル
# Scanメソッド
[0.355ms] [rows:4] SELECT "name" FROM "users"

Scanメソッド:特定カラムを抽出

finisher_api.go
func (db *DB) Scan(dest interface{}) (tx *DB)

一方、ジェネリクスAPIを用いた特定カラムの抽出は、以下のように実装することができます。

main.go
// 抽出に関わる部分のみ抜粋
userNames := []UserName{}
if err := gorm.G[User](db).Scan(ctx, &userNames); err != nil {
    log.Fatal(err)
}

実行されるSQLは従来の場合と同じです。

ターミナル
# Scanメソッド
# "users"."name"と明示的にテーブル名が指定されている
[0.528ms] [rows:4] SELECT "users"."name" FROM "users"

Scanメソッド

特定カラムを抽出

generics.go
func (g execG[T]) Scan(ctx context.Context, result interface{}) error

まとめ

今回は、GORM v1.30で追加されたジェネリクスAPIを使って、型安全にレコードを抽出する方法を見ていきました。

  • 単一レコードの抽出
    • First(主キー昇順の最初)
    • Take(条件一致1件、順序保証なし)
    • Last(主キー降順の最初)
  • 複数レコードの抽出
    • Find(複数レコードの抽出)
  • 特定カラムの抽出
    • Scan(特定カラムの抽出)

次回は、「ジェネリクスAPIを使って型安全にレコードを更新する方法」について見ていきます!

参考

Discussion