1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. ORMs
  5. GORM - Golang learning step 4-1

GORM - Golang learning step 4-1

  • 公開日
  • カテゴリ:ORMs
  • タグ:Golang,roadmap.sh,学習メモ
GORM - Golang learning step 4-1

roadmap.sh > Go > ORMs > GORM の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. GORM
    1. ORM
    2. 主な特徴
  4. GORM のインストール
  5. 1. データベース接続
  6. 2. モデルの定義
  7. 3. マイグレーション
  8. 4. データの操作
    1. 作成(Create)
    2. 取得(Read)
    3. 更新(Update)
    4. 削除(Delete)
    5. 結合(join)
  9. 5. トランザクションの使用
  10. 6. リレーション(Preload)
    1. モデルのリレーションを設定
    2. Preload の使用方法
    3. Preload を使用したデータ取得
    4. 実践的な使用例

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

GORM

GORM は、Golang 向けのリレーショナルデータベースを扱うための ORM ライブラリです。GORM ライブラリは、database/sql パッケージを基盤として開発されています。ほぼ全てのリレーショナルデータベース操作(CRUD、リレーション管理、トランザクション管理、プリロードなど)を抽象化し、容易に実装できます。

ORM

ORM(オブジェクト関係マッピング、O/RM、O/Rマッピングツール)は、コンピュータサイエンスにおいて、オブジェクト指向プログラミング言語を使用して異なる型システム間でデータを変換するプログラミング技術です。これにより実質的に「仮想オブジェクトデータベース」が作成され、プログラミング言語から利用可能な抽象化層が形成されます。Go 言語における最も一般的な ORM ライブラリは GORM です。

主な特徴

  1. 自動マイグレーション機能
    • 構造体の定義からデータベーススキーマを自動生成
    • テーブルやカラムの作成・更新を自動化
  2. 豊富なデータベース対応
    • MySQL
    • PostgreSQL
    • SQLite
    • SQL Server
  3. チェーンメソッド
    • メソッドを連鎖させて直感的なクエリを記述可能

GORM のインストール

GoのプロジェクトでGORMを使用するには、以下のコマンドで必要なライブラリをインストールします。

go get -u gorm.io/gorm

go get -u gorm.io/driver/mysql    # MySQL を使用する場合
go get -u gorm.io/driver/postgres # PostgreSQL を使用する場合
go get -u gorm.io/driver/sqlite   # SQLite を使用する場合

※本記事では PostgreSQL で進めます。

1. データベース接続

PostgreSQLに接続するために、pgx ドライバを使用します。以下は接続の基本コードです。

package main

import (
  "fmt"
  "log"

  "gorm.io/driver/postgres"
  "gorm.io/gorm"
)

func main() {
  // PostgresSQL 接続用の DSN(データソース名)
  dsn := "host=localhost user=youruser password=yourpassword dbname=yourdb port=5432 sslmode=disable TimeZone=Asia/Tokyo"

  // データベース接続
  db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
  if err != nil {
    log.Fatal("データベース接続エラー: ", err)
  }

  log.Println("PostgresSQL に接続しました")
}
  • host: PostgreSQLサーバーのホスト名(通常はlocalhost)。
  • user: PostgreSQLのユーザー名。
  • password: ユーザーのパスワード。
  • dbname: 使用するデータベース名。
  • port: PostgreSQLのポート(デフォルトは5432)。
  • sslmode: SSL接続のモード(開発環境ではdisable)。
  • TimeZone: タイムゾーン設定。

2. モデルの定義

データベースに対応するテーブルを表現するモデルを定義します。

type User struct {
  ID    uint   `gorm:"primaryKey"`        // 主キー
  Name  string `gorm:"size:100;not null"` // 最大100文字、必須
  Email string `gorm:"unique;not null"`   // ユニーク制約、必須
}

3. マイグレーション

AutoMigrate を使用して、PostgreSQL にテーブルを作成します。

func main() {
  // PostgreSQL接続(省略)

  // テーブルのマイグレーション
  err := db.AutoMigrate(&User{})
  if err != nil {
    log.Fatal("マイグレーションエラー: ", err)
  }

  log.Println("マイグレーションが成功しました")
}

4. データの操作

作成(Create)

単一レコードの作成

user := User{Name: "山田太郎", Email: "yamada@example.com"}
result := db.Create(&user)
if result.Error != nil {
  log.Println("登録に失敗しました: ", result.Error)
}
log.Printf("登録成功: ID=%d\n", user.ID)

複数レコードの一括作成

users := []User{
  {Name: "佐藤一郎", Email: "sato@example.com"},
  {Name: "鈴木次郎", Email: "suzuki@example.com"},
}
result := db.Create(&users)
if result.Error != nil {
  log.Println("登録に失敗しました: ", result.Error)
}

var ids []uint
for _, user := range users {
  ids = append(ids, user.ID)
}
log.Printf("登録成功: IDs=%v\n", ids)

取得(Read)

プライマリーキーで検索

var user User
db.First(&user, 1) // ID が 1 のユーザーを取得
log.Println(user)

条件指定での検索

var user User
db.Where("email = ?", "sato@example.com").First(&user)
log.Println(user)

更新(Update)

単一カラムの更新

db.Model(&user).Where("email = ?", "suzuki@example.com").Update("Name", "鈴木二郎")

複数カラムの更新

user := User{Name: "山田 太郎", Email: "taro@example.com"}
db.Model(&user).Where("id = ?", 1).Updates(user)

削除(Delete)

db.Where("id = ?", 1).Delete(&user)

結合(join)

モデル:

type User struct {
  ID      uint        `gorm:"primaryKey"`
  Name    string      `gorm:"size:100;not null"`
  Email   string      `gorm:"unique;not null"`
  Profile *UserProfile `gorm:"foreignKey:UserID"` // リレーションフィールドを追加
}

type UserProfile struct {
  ID          uint   `gorm:"primaryKey"`
  UserID      uint   `gorm:"unique;not null"`
  Age         int    `gorm:"not null"`
  PhoneNumber string `gorm:"size:20"`
  User        *User  `gorm:"foreignKey:UserID"` // リレーションフィールドを追加
}

データ:

users:
2,佐藤一郎,sato@example.com
3,鈴木二郎,suzuki@example.com
4,斎藤 花子,hanako@example.com

user_profiles:
1,2,25,090-1234-5678
2,4,30,090-8765-4321
  1. 内部結合(INNER JOIN)
var users []User
db.InnerJoins("Profile").Find(&users)

for _, user := range users {
  fmt.Printf("name: %s (age: %d)\n", user.Name, user.Profile.Age)
}

実行結果:

name: 佐藤一郎 (age: 25)
name: 斎藤 花子 (age: 30)
  1. 左結合(LEFT JOIN)
var users []User
db.Joins("Profile").Find(&users)

for _, user := range users {
  var age string
  if user.Profile == nil {
    age = "記載なし"
  } else {
    age = strconv.Itoa(user.Profile.Age)
  }
  fmt.Printf("name: %s (age: %s)\n", user.Name, age)
}

実行結果:

name: 佐藤一郎 (age: 25)
name: 斎藤 花子 (age: 30)
name: 鈴木二郎 (age: 記載なし)

5. トランザクションの使用

GORM はトランザクションを扱えます。

func main() {
  db := dbConnection()

  // トランザクションの開始
  err := db.Transaction(func(tx *gorm.DB) error {
    user := User{Name: "斎藤花子", Email: "hanako@example.com"}
    if err := tx.Create(&user).Error; err != nil {
      return err // エラーが発生した場合ロールバック
    }

    user.Name = "斎藤 花子"
    if err := tx.Model(&user).Update("Name", "斎藤 花子").Error; err != nil {
      return err // エラーが発生した場合ロールバック
    }

    return nil // エラーが無ければコミット
  })

  if err != nil {
    log.Println("トランザクションエラー:", err)
  } else {
    log.Println("トランザクション成功")
  }
}

6. リレーション(Preload)

Preload は、GORM でリレーション(関連)を扱う際に、関連するデータを効率よく取得するための方法です。これにより、N+1 問題を回避しつつ、1 回のクエリで必要なデータを取得できます。

ベースとなるモデル

type Book struct {
  ID          uint      `gorm:"primaryKey"`
  Name        string    `gorm:"size:100;not null"`
  PublishDate time.Time `gorm:"not null"`
}

type Author struct {
  ID     uint   `gorm:"primaryKey"`
  BookID uint   `gorm:"unique;not null"`
  Name   string `gorm:"size:100;not null"`
}

モデルのリレーションを設定

モデルに関連付けを行います。リレーションフィールドを追加します。

type Book struct {
  ID          uint      `gorm:"primaryKey"`
  Name        string    `gorm:"size:100;not null"`
  PublishDate time.Time `gorm:"not null"`
  Author      Author    `gorm:"foreignKey:BookID"` // 1:1 の関連を追加
}

type Author struct {
  ID     uint   `gorm:"primaryKey"`
  BookID uint   `gorm:"unique;not null"`
  Name   string `gorm:"size:100;not null"`
  Book   *Book  `gorm:"foreignKey:BookID"` // 逆方向の関連
}

1:1 の双方向の関連を定義する場合、循環参照を避けるためにどちらか一方をポインタ型にする必要があります。(Author の Book)

Preload の使用方法

Preload は、関連するデータを事前に読み込む GORM の機能です。1:1 のリレーションにおいても、N+1問題を防ぐために重要です。

N+1問題の例(Preload を使用しない場合)

db := dbConnection()
var books []Book
db.Find(&books) // 1回目のクエリ

// 各本に対して著者を個別に取得(N回のクエリ)
for _, book := range books {
  var author Author
  db.Where("book_id = ?", book.ID).First(&author)
}

この場合の SQL:

SELECT * FROM books;
SELECT * FROM authors WHERE book_id = 1 LIMIT 1;
SELECT * FROM authors WHERE book_id = 2 LIMIT 1;
SELECT * FROM authors WHERE book_id = 3 LIMIT 1;
...

Preload を使用したデータ取得

var books []Book
db.Preload("Author").Find(&books)

この場合の SQL:

SELECT * FROM books;
SELECT * FROM authors WHERE book_id IN (1,2,3,...);

実践的な使用例

  1. 本と著者の情報を同時に取得
db := dbConnection()
var book Book
bookID := 1
result := db.Preload("Author").First(&book, bookID)
if result.Error != nil {
  log.Fatal(result.Error)
}
fmt.Printf("book %+v\n", book)

// => book {ID:1 Name:Book 1 PublishDate:2023-01-01 00:00:00 +0000 UTC Author:{ID:1 BookID:1 Name:Author 1 Book:<nil>}}
  1. 条件付きの検索
db := dbConnection()

// 特定の出版日以降の本とその著者を取得

var books []Book
date := time.Date(2023, 4, 7, 0, 0, 0, 0, time.UTC)
result := db.Preload("Author").
  Where("publish_date >= ?", date).
  Find(&books)
if result.Error != nil {
  log.Fatal(result.Error)
}

// データの表示
for _, book := range books {
  fmt.Printf("書籍: %s (出版日: %s)\n",
    book.Name,
    book.PublishDate.Format("2006-01-02"))
  fmt.Printf("著者: %s\n\n", book.Author.Name)
}

実行結果:

書籍: Book 97 (出版日: 2023-04-07)
著者: Author 97

書籍: Book 98 (出版日: 2023-04-08)
著者: Author 98

書籍: Book 99 (出版日: 2023-04-09)
著者: Author 99

書籍: Book 100 (出版日: 2023-04-10)
著者: Author 100

まとめ

  • ORM はリレーショナルデータベース操作を抽象化し、効率的なデータ処理を可能にするプログラミング技術
  • GORM は Go 言語向けの人気 ORM ライブラリ
  • 自動マイグレーションやリレーション、Preload などの便利な機能を備える GORM
  • モデルの定義と AutoMigrate を使用したスキーマの自動生成
  • CRUD 操作を簡単に実現できる GORM のメソッド
  • Preload を活用した N+1 問題の回避と効率的なリレーションデータ取得
  • トランザクション機能によるデータ整合性の確保

機能はまだまだたくさんあるので、公式ドキュメントを参照



[Next] Step 5-1: Gin

[Prev] Step 3-2: Cobra

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級