はじめに
「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」6日目の記事では、GoにおけるORMの役割と、基本的な操作について学びます。
前日の記事で、Goの標準ライブラリdatabase/sqlを使ってDB操作の基本を学びました。
それにしても、SQLを直接実行する方法は「毎回SQL文を書くのは大変」「コードが少し冗長だな…」と感じた方もいるのではないでしょうか。
その課題を解決し、開発の生産性を飛躍的に向上させてくれるのがORM (Object-Relational Mapper)です。
この記事では、GoのORMの一つであるGORMを取り上げ、database/sqlと比べてどのようにCRUD操作を簡潔にしてくれるのかを紹介します。
その上で、標準ライブラリではなくGORMを使うことのメリットについても解説します。
-
database/sqlでの開発からステップアップしたい方 - GoのORM、特にGORMに初めて触れる方
- マイグレーションの具体的な挙動や注意点を知りたい方
ORMについて「なにそれ美味しいの?」状態でも、基本から学んで効率的なDB処理ができるように、頑張りましょう!
ORMとGORMの基本
database/sqlではSQL文を直接書いていましたが、ORMを使うとGoのオブジェクトやメソッドを通じてデータベースを操作できます。
go mod initをした状態から始めていきます。
まずは、GORMをインストールしてみましょう。
go get gorm.io/gorm
go get gorm.io/driver/postgres # PostgreSQLドライバ
GORMによるCRUD操作
database/sqlと比較して、コードがどれだけシンプルになるか、以下の例を見てみましょう。
コードの一部抜粋なので、こんな感じで書くんだな、くらいの理解で大丈夫です。
// Productモデルの定義
type Product struct {
gorm.Model // ID, CreatedAt, UpdatedAt, DeletedAt を含む
Name string
Price uint
}
// 接続 (main関数内)
dsn := "host=localhost user=myuser password=mypassword dbname=mydb port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("データベースの接続に失敗しました")
}
// DBマイグレーション
err = db.AutoMigrate(&Product{})
if err != nil {
log.Fatalf("マイグレーションに失敗しました: %v", err)
}
fmt.Println("データベースのマイグレーションに成功しました")
// --- CRUD操作 ---
// Create
db.Create(&Product{Name: "Laptop", Price: 150000})
// Read (First)
var product Product
db.First(&product, 1) // id=1 の製品を検索
// Update
db.Model(&product).Update("Price", 145000)
// Delete
db.Delete(&product, 1)
fmt.Println("CRUD処理が全て成功しました")
db.Createやdb.Firstといったメソッドで直感的に操作できているのがわかります。
このように単純に操作できるのが、ORMのメリットです。
GORMのスキーマ管理:マイグレーションとは?
さて、本日のテーマは上記コード中にあったdb.AutoMigrate(&Product{})の記述に注目します。
Webアプリケーションを開発・運用していると、「ユーザーテーブルにニックネーム用のカラムを追加したい」といった、データベースの構造(スキーマ)の変更は日常的に発生します。
マイグレーションとは、このようなスキーマの変更を、コードで管理し、バージョン管理できるようにする仕組みのことです。
GORMのAutoMigrateは、このプロセスを簡易的に行うための機能です。
GORMのAutoMigrate の仕組み
AutoMigrateは、Goの構造体の定義を現在のデータベースの状態と比較し、不足しているものだけを追加します。
db.AutoMigrate(&Product{}) を実行すると、GORMは内部で以下のような処理を行います。
-
Product構造体に対応するテーブル(通常は複数形でproducts)が存在するかチェックします。 - テーブルが存在しなければ、
CREATE TABLE文を実行してテーブルを新規作成します。 - テーブルが存在する場合、
Product構造体にあるフィールド、インデックス、外部キー制約などをチェックします。 - もしデータベースのテーブルに存在しないカラムやインデックスがあれば、自動でクエリを実行して追加します。
破壊的な変更は行わない
AutoMigrateは、GORMは既存のカラムの削除や型の変更といった、データ損失の危険性がある「破壊的な操作」を自動で行いません。
これにより、開発者が誤って構造体のフィールドを消してしまったときに、本番データベースの大切なカラムが消し飛んでしまう、といった事態が防げます。
【実践】モデルの変更とマイグレーションの挙動
それでは、実際にコードを動かしてAutoMigrateの挙動を確かめてみましょう。
main.goに以下のコードを記載します。
package main
import (
"fmt"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// Product モデル
type Product struct {
gorm.Model
Name string
Price uint
}
func main() {
dsn := "host=localhost user=myuser password=mypassword dbname=mydb port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("データベースとの接続に失敗しました")
}
// マイグレーション実行
fmt.Println("マイグレーションを実行しています...")
db.AutoMigrate(&Product{})
fmt.Println("マイグレーションが成功しました")
}
上記のコードを実行すると、productsテーブルが作成されます。
シナリオ1:新しいフィールドを追加する
Product構造体に、商品コードを保存するためのCodeフィールドを追加してみましょう。
type Product struct {
gorm.Model
Name string
Price uint
Code string `gorm:"unique"` // Codeフィールドを追加し、unique制約も付与
}
この状態で再度main関数を実行すると、GORMはproductsテーブルにcodeカラムが存在しないことを検知し、既存のデータを保持したまま、新しいカラムが追加されます。
シナリオ2:フィールドを削除する
次に、Product構造体からPriceフィールドをコメントアウト(削除)してみましょう。
type Product struct {
gorm.Model
Name string
// Price uint // Priceフィールドを削除
Code string `gorm:"unique"`
}
この状態でmain.goを実行しても、GORMは何も行いません。
コンソールにはマイグレーションが成功しましたと表示されますが、データベースのproductsテーブルを確認すると、priceカラムは削除されずに残っています。
これは前述の通り、GORMがデータ損失を防ぐために、破壊的な変更であるカラムの削除を自動で行わないためです。
シナリオ3:フィールドの型を変更する
最後に、Priceの型をuint(整数)からstringに変更してみます。
type Product struct {
gorm.Model
Name string
Price string // uint から string に型を変更
Code string `gorm:"unique"`
}
この状態でmain.goを実行しても、やはりGORMは何も行いません。
priceカラムの型はbigint(uintに対応)のままです。型の変更は、既存のデータが新しい型と互換性がない場合にデータ損失を引き起こす可能性があるため、GORMは自動での変更を行いません。
このような破壊的な変更が必要な場合は、本格的なマイグレーションツールを使い、手動でSQLを記述する必要があります。
AutoMigrateの使い所と限界
ここまでの検証を踏まえて、個人的に感じたAutoMigrateのメリットとデメリットを洗い出してみます。
メリット
- 開発が高速化する: 開発の初期段階やプロトタイピングにおいて、構造体の変更に合わせてDBスキーマを素早く同期できる。
- 追加漏れを防げる: 新しいフィールドを追加した際に、ヒューマンエラーを防げる。
デメリット
- 完全なマイグレーションツールではない: カラムの削除、リネーム、型の変更といった破壊的な変更はサポートしていない。
-
バージョン管理ができない:
AutoMigrateはスキーマ変更の履歴をバージョン管理しないのでロールバックなどができない
おわりに
この記事では、GoのORMであるGORMの基本を紹介し、そのマイグレーション機能の仕組みと使い方について深掘りしました。
GORMはとても便利なツールである一方で、マイグレーションに少し難点があるので大規模な開発を行う際は、他のマイグレーションツールの導入なども検討するといいのかな、と思いました。
最後までお読みいただき、ありがとうございました!
この「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」では、GoやJavaを使い、APIの作り方、データベースとの接続、テストやDockerといった気になったバックエンド技術の基本を振り返った学びを共有しています。
ぜひ他の記事もチェックして、筆者がこのひとりアドカレを完遂することができるか、確認してみてください(^^)
学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025
参考文献