GORM 是一個受歡迎的 Go ORM,透過 gorm.io/driver/sqlserver 方言支援 SQL Server,並在內部使用 go-mssqldb。 GORM 處理結構差異、查詢產生和型別映射,讓你能直接用 Go 結構體,而不是寫原始 SQL。
本文介紹了 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"
}
小提示
使用現有的 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)
}
擷取單一資料列
使用 First 條件載入一條紀錄:
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"
}
插入一列
傳送一個已填入的結構體 to 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 with 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 每個語句限制sp_executesql為 2,100 個參數。 GORM 的批次插入會產生一個多資料列的 VALUES 子句,其中每個欄位都會占用一個參數,因此,對於有 10 個欄位的資料表來說,每批大約在 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 會話的同時切換到 raw 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 管理的同一個連線池,因此池 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。