GORM はgorm.io/driver/sqlserver方言でSQL Serverをサポートする人気の Go ORM で、内部で go-mssqldb を使用しています。 GORM はスキーマの違い、クエリ生成、型マッピングを処理しているので、生のSQLを書く代わりにGo構造体で作業できます。
この記事では、SQL Server特有のGORMパターンについて取り上げます。 GORMの一般的な使用方法については、GORM ドキュメントをご覧ください。
この記事の例は AdventureWorks2025 のサンプルデータベースと比較しています。
GORM と SQL Server 方言をインストールしてください
Goモジュールを作成または開き、GORMとSQL Server方言を追加してください:
mkdir go-gorm-sqlserver
cd go-gorm-sqlserver
go mod init example.com/go-gorm-sqlserver
go get gorm.io/gorm
go get gorm.io/driver/sqlserver
SQL Serverに接続する
sqlserver://DSNを方言に渡してGORMセッションを開きます:
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
func main() {
// Replace <server>, <user>, and <password> with your own values.
// In production, load credentials from environment variables or a secret store.
dsn := "sqlserver://<user>:<password>@<server>.database.windows.net:1433?database=AdventureWorks2025&encrypt=true&TrustServerCertificate=false"
db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
sqlDB, err := db.DB()
if err != nil {
log.Fatal(err)
}
defer sqlDB.Close()
fmt.Println("Connected to SQL Server with GORM")
}
方言は内部で go-mssqldb ドライバーを使用しているため、 接続文字列 や 接続オプション のすべての接続オプションがDSNに適用されます。
既存のテーブルへのモデルマッピング
GORM のデフォルトの命名規則は小文字の複数形テーブル名(例:products)を使用しており、SQL Serverの慣例とは一致しません。
TableName()メソッドを使って、モデルを既存のスキーマ限定テーブルにバインドします:
type Product struct {
ProductID int32 `gorm:"column:ProductID;primaryKey"`
Name string `gorm:"column:Name"`
ProductLine *string `gorm:"column:ProductLine"`
ListPrice float64 `gorm:"column:ListPrice"`
}
func (Product) TableName() string {
return "Production.Product"
}
Tip
既存のSQL Serverデータベースを扱う際は、必ずTableName()を定義してください。 GORM の自動複数化に頼ると、SQL Server 命名規則に従うテーブルに対して「無効なオブジェクト名」エラーが発生します。
データのクエリを実行する
モデルを定義した後、GORM のクエリメソッドを使ってデータを読み込みます:
WHERE句を使って行を検索する
Where、Order、Limitを使って、結果を絞り込み、並べ替え、制限します。
var products []Product
result := db.Where("ListPrice > ?", 1000).
Order("ListPrice DESC").
Limit(5).
Find(&products)
if result.Error != nil {
log.Fatal(result.Error)
}
for _, p := range products {
fmt.Printf("%-35s $%.2f\n", p.Name, p.ListPrice)
}
1 つの行を取得する
Firstを条件付きで1つのレコードを読み込む:
var product Product
if err := db.First(&product, "ProductID = ?", 680).Error; err != nil {
log.Fatal(err)
}
fmt.Printf("%s: $%.2f\n", product.Name, product.ListPrice)
行数を数える
モデルスコープで行数を取るために Count を使う:
var count int64
db.Model(&Product{}).Where("ListPrice > ?", 0).Count(&count)
fmt.Printf("Products with a list price: %d\n", count)
行の挿入、更新、削除
書き込み操作は同じモデル構造体を使用します。 このセクションの例は、重要なテーブルを改変しないために、 Production.ScrapReason をターゲットにしています。
type ScrapReason struct {
ScrapReasonID int16 `gorm:"column:ScrapReasonID;primaryKey;autoIncrement"`
Name string `gorm:"column:Name"`
ModifiedDate time.Time `gorm:"column:ModifiedDate"`
}
func (ScrapReason) TableName() string {
return "Production.ScrapReason"
}
行を挿入してください
値が設定された構造体を Create に渡して行を挿入し、自動生成された主キーを設定します:
reason := ScrapReason{Name: "GORM test scrap", ModifiedDate: time.Now()}
if err := db.Create(&reason).Error; err != nil {
log.Fatal(err)
}
fmt.Printf("Inserted ScrapReasonID: %d\n", reason.ScrapReasonID)
// Clean up
db.Delete(&reason)
行を更新する
Model をUpdateで使って列の値を変更する:
reason := ScrapReason{Name: "GORM update test", ModifiedDate: time.Now()}
db.Create(&reason)
db.Model(&reason).Update("Name", "GORM updated scrap")
var updated ScrapReason
db.First(&updated, reason.ScrapReasonID)
fmt.Printf("Updated name: %s\n", updated.Name)
// Clean up
db.Delete(&reason)
行の削除
プライマリキーが埋め込まれた構造体を Deleteに渡します:
reason := ScrapReason{Name: "GORM delete test", ModifiedDate: time.Now()}
db.Create(&reason)
id := reason.ScrapReasonID
db.Delete(&reason)
var count int64
db.Model(&ScrapReason{}).Where("ScrapReasonID = ?", id).Count(&count)
fmt.Printf("Rows remaining with that ID: %d\n", count)
バッチ挿入と2,100パラメータの制限
SQL Server 1文あたりsp_executesqlパラメータ数を2,100に制限しています。 GORM のバッチ挿入は、各列が1つのパラメータを消費する多行 VALUES 節を生成するため、10列のテーブルは1バッチあたり約210行の上限に達します。
制限内に収めるため、セッションで CreateBatchSize を設定します:
reasons := make([]ScrapReason, 50)
for i := range reasons {
reasons[i] = ScrapReason{
Name: fmt.Sprintf("Batch scrap %d", i+1),
ModifiedDate: time.Now(),
}
}
// 2 inserted columns per row (Name, ModifiedDate; the autoIncrement ID is excluded),
// so batches of 500 are well within the 2,100-parameter limit.
result := db.CreateInBatches(&reasons, 500)
if result.Error != nil {
log.Fatal(result.Error)
}
fmt.Printf("Inserted %d rows in batches\n", result.RowsAffected)
// Clean up
for _, r := range reasons {
db.Delete(&r)
}
列が多いテーブルでは、バッチサイズを比例して減らしましょう。 安全なフォーミュラ: batch size = floor(2100 / number of inserted columns)。
Transactions
GORM トランザクション内で複数の操作を実行する:
err := db.Transaction(func(tx *gorm.DB) error {
r1 := ScrapReason{Name: "TX scrap 1", ModifiedDate: time.Now()}
if err := tx.Create(&r1).Error; err != nil {
return err
}
r2 := ScrapReason{Name: "TX scrap 2", ModifiedDate: time.Now()}
if err := tx.Create(&r2).Error; err != nil {
return err
}
// Both inserts commit together.
fmt.Printf("Inserted IDs: %d, %d\n", r1.ScrapReasonID, r2.ScrapReasonID)
// Clean up inside the same transaction.
tx.Delete(&r1)
tx.Delete(&r2)
return nil
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Transaction committed")
コールバックがエラーを返すと、GORM は自動的にロールバックします。
生のSQLフォールバック
GORM のクエリビルダーが必要な SQL Server 構文を生成できない場合は、GORM セッションを使いながら生のSQLに切り替えてください:
var results []struct {
Name string
ListPrice float64
}
result := db.Raw("SELECT TOP 5 Name, ListPrice FROM Production.Product WHERE ListPrice > @price ORDER BY ListPrice DESC",
sql.Named("price", 1000)).Scan(&results)
if result.Error != nil {
log.Fatal(result.Error)
}
for _, r := range results {
fmt.Printf("%-35s $%.2f\n", r.Name, r.ListPrice)
}
生クエリはGORM(ORM管理)と同じ接続プールを使用しているため、 db.DB() のプール設定は引き続き適用されます。
接続プールの設定
プールパラメータを設定するために基礎 *sql.DB にアクセスしてください:
sqlDB, err := db.DB()
if err != nil {
log.Fatal(err)
}
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(5 * time.Minute)
プールのサイズ調整に関するガイダンスについては 、接続プーリングを参照してください。
AutoMigrateの考慮事項
AutoMigrate モデルの定義に合わせてテーブルを作成または変更します。 SQL Serverでも動作しますが、以下の動作に注意してください:
-
AutoMigrateデフォルトのスキーマ(dbo)でテーブルを作成しますが、スキーマプレフィックスでTableName()をオーバーライドしない限り。 - 欠落した列を追加したりインデックスを作成したりしますが、列を削除したり既存の列タイプを変更したりはしません。
- SQL Serverはインデックス名に128文字の制限があります。 GORM 生成されるインデックス名は、長い名前や複合インデックスを持つテーブルではこの制限を超えることがあります。 この制限に達したら明示的な
gorm:"index:idx_short_name"タグを使いましょう。
管理スキーマ付きの本番データベースでは、よりもAutoMigrateを好む。