GORM - Golang learning step 4-1
- 公開日
- カテゴリ:ORMs
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > ORMs > GORM の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
- 開発環境
- 参考 URL
- GORM
- GORM のインストール
- 1. データベース接続
- 2. モデルの定義
- 3. マイグレーション
- 4. データの操作
- 5. トランザクションの使用
- 6. リレーション(Preload)
開発環境
- チップ: 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 です。
主な特徴
- 自動マイグレーション機能
- 構造体の定義からデータベーススキーマを自動生成
- テーブルやカラムの作成・更新を自動化
- 豊富なデータベース対応
- MySQL
- PostgreSQL
- SQLite
- SQL Server
- チェーンメソッド
- メソッドを連鎖させて直感的なクエリを記述可能
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
- 内部結合(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)
- 左結合(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,...);
実践的な使用例
- 本と著者の情報を同時に取得
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>}}
- 条件付きの検索
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