在 Go 語言社區中,數據庫交互一直是開發者們關注的重點領域,不同開發者基于自身的需求和偏好,形成了兩種主要的技術選型流派。一部分開發者鐘情于像sqlx
這類簡潔的庫,盡管其功能并非一應俱全,但它賦予開發者對 SQL 語句的絕對控制權,便于開發者將性能優化到極致,從而滿足對性能有嚴苛要求的場景。與之相對的另一部分開發者,則更傾向于為提升開發效率而生的 ORM(對象關系映射)框架。借助 ORM,開發者能夠省去諸多繁瑣的數據庫操作細節,極大地加速開發進程。
在 Go 語言的 ORM 領域,gorm
無疑占據著舉足輕重的地位,作為一款歷史悠久且成熟的 ORM 框架,深受廣大開發者的喜愛。與gorm
類似的,還有相對年輕的xorm
和ent
等框架,它們各自憑借獨特的優勢,在 Go 語言社區中也擁有著一批忠實的用戶。
本文聚焦于gorm
框架,主要為大家介紹其基礎入門知識,希望能為讀者開啟探索gorm
世界的大門。若想深入了解gorm
的更多細節,推薦閱讀官方文檔,其完善的中文文檔為開發者提供了詳盡的學習資料。
- 官方文檔:GORM - The fantastic ORM library for Golang, aims to be developer friendly.
- 開源倉庫:go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)
特點
- 全功能 ORM:涵蓋了豐富的數據庫操作功能,為開發者提供一站式解決方案。
- 關聯關系支持:全面支持多種關聯關系,如擁有一個(Has One)、擁有多個(Has Many)、屬于(Belongs To)、多對多(Many To Many)、多態(Polymorphism)以及單表繼承(Single-table inheritance),滿足復雜業務場景下的數據關系建模需求。
- 鉤子方法:在 Create、Save、Update、Delete、Find 等操作中均提供了鉤子方法,方便開發者在數據庫操作前后進行自定義邏輯處理。
- 預加載功能:支持
Preload
和Joins
的預加載方式,有效減少數據庫查詢次數,提升數據獲取效率。 - 事務管理:提供完善的事務管理機制,包括事務、嵌套事務、Save Point 以及 Rollback To Saved Point 等功能,確保數據操作的原子性和一致性。
- 多種模式支持:支持 Context、預編譯模式(Prepared Statement Mode)和 DryRun 模式,為開發者提供更多的靈活性和調試便利性。
- 高效的數據操作:具備批量插入、FindInBatches、Find/Create with Map 等功能,并且支持使用 SQL 表達式、Context Valuer 進行 CRUD 操作,滿足不同場景下的數據操作需求。
- 強大的 SQL 構建能力:擁有 SQL 構建器,支持 Upsert、鎖機制、Optimizer/Index/Comment Hint、命名參數以及子查詢等高級 SQL 特性,讓開發者能夠靈活構建復雜的 SQL 語句。
- 數據庫結構管理:支持復合主鍵、索引和約束的創建與管理,同時提供自動遷移功能,能夠根據定義的結構體自動同步數據庫表結構,減少手動維護數據庫結構的工作量。
- 自定義日志:允許開發者自定義 Logger,方便記錄和跟蹤數據庫操作日志,便于排查問題和進行性能分析。
- 靈活的插件擴展:提供靈活可擴展的插件 API,例如 Database Resolver(支持多數據庫、讀寫分離)、Prometheus 等插件,滿足不同業務場景下的擴展需求。
- 嚴格的測試保障:每個特性都經過了嚴格的測試,確保框架的穩定性和可靠性。
- 開發者友好:設計理念注重開發者體驗,提供簡潔易懂的 API 和豐富的文檔,降低開發者的學習成本。
當然,gorm
并非完美無缺。例如,其幾乎所有方法的參數都采用空接口類型,這使得參數的傳遞方式較為模糊,若不查閱文檔,開發者很難明確在不同場景下應傳遞何種參數,有時可傳遞結構體,有時是字符串、map 或切片。此外,在許多情況下,開發者仍需自行編寫 SQL 語句來滿足復雜的業務需求。
其中,gorm
作為 Go 生態中歷史悠久的 ORM 框架,憑借其全面的功能支持和良好的社區活躍度,成為眾多項目的首選。本文將圍繞gorm
展開基礎入門介紹,旨在幫助快速上手。
安裝
$ go get -u gorm.io/gorm
gorm 目前支持以下幾種數據庫
- MySQL :
"gorm.io/driver/mysql"
- PostgreSQL:
"gorm.io/driver/postgres"
- SQLite:
"gorm.io/driver/sqlite"
- SQL Server:
"gorm.io/driver/sqlserver"
- TIDB:
"gorm.io/driver/mysql"
,TIDB 兼容 mysql 協議 - ClickHouse:
"gorm.io/driver/clickhouse"
本文接下來將使用 MySQL 來進行演示,使用的什么數據庫,就需要安裝什么驅動,這里安裝 Mysql 的 gorm 驅動。
$ go get -u gorm.io/driver/mysql # 以MySQL為例
然后使用 dsn(data source name)連接到數據庫,驅動庫會自行將 dsn 解析為對應的配置
package mainimport ("gorm.io/driver/mysql""gorm.io/gorm""log/slog"
)func main() {dsn := "root:123456@tcp(192.168.48.138:3306)/hello?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn))if err != nil {slog.Error("db connect error", err)}slog.Info("db connect success")
}
或者手動傳入配置
package mainimport ("gorm.io/driver/mysql""gorm.io/gorm""log/slog"
)func main() {db, err := gorm.Open(mysql.New(mysql.Config{}))if err != nil {slog.Error("db connect error", err)}slog.Info("db connect success")
}
兩種方法都是等價的,看自己使用習慣。
連接配置
通過傳入gorm.Config
配置結構體,我們可以控制 gorm 的一些行為
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
模型
gorm.Model
為了方便模型定義,GORM內置了一個gorm.Model
結構體。gorm.Model
是一個包含了ID
, CreatedAt
, UpdatedAt
, DeletedAt
四個字段的Golang結構體。
// gorm.Model 定義
type Model struct {ID uint `gorm:"primary_key"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt *time.Time
}
你可以將它嵌入到你自己的模型中:
// 將 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`字段注入到`User`模型中
type User struct {gorm.Model // 內嵌gorm.Model,包含ID, CreatedAt, UpdatedAt, DeletedAt字段 Name string `gorm:"type:varchar(100);not null"`Email string `gorm:"type:varchar(100);uniqueIndex"`Age int `gorm:"default:18"`IsActive bool `gorm:"default:true"`
}
當然你也可以完全自己定義模型:
// 不使用gorm.Model,自行定義模型
type User struct {ID intName string
}
結構體標記(tags)
使用結構體聲明模型時,標記(tags)是可選項。gorm支持以下標記:
支持的結構體標記(Struct tags)
結構體標記(Tag) | 描述 |
---|---|
Column | 指定列名 |
Type | 指定列數據類型 |
Size | 指定列大小, 默認值255 |
PRIMARY_KEY | 將列指定為主鍵 |
UNIQUE | 將列指定為唯一 |
DEFAULT | 指定列默認值 |
PRECISION | 指定列精度 |
NOT NULL | 將列指定為非 NULL |
AUTO_INCREMENT | 指定列是否為自增類型 |
INDEX | 創建具有或不帶名稱的索引, 如果多個索引同名則創建復合索引 |
UNIQUE_INDEX | 和 INDEX 類似,只不過創建的是唯一索引 |
EMBEDDED | 將結構設置為嵌入 |
EMBEDDED_PREFIX | 設置嵌入結構的前綴 |
- | 忽略此字段 |
關聯相關標記(tags)
結構體標記(Tag) | 描述 |
---|---|
MANY2MANY | 指定連接表 |
FOREIGNKEY | 設置外鍵 |
ASSOCIATION_FOREIGNKEY | 設置關聯外鍵 |
POLYMORPHIC | 指定多態類型 |
POLYMORPHIC_VALUE | 指定多態值 |
JOINTABLE_FOREIGNKEY | 指定連接表的外鍵 |
ASSOCIATION_JOINTABLE_FOREIGNKEY | 指定連接表的關聯外鍵 |
SAVE_ASSOCIATIONS | 是否自動完成 save 的相關操作 |
ASSOCIATION_AUTOUPDATE | 是否自動完成 update 的相關操作 |
ASSOCIATION_AUTOCREATE | 是否自動完成 create 的相關操作 |
ASSOCIATION_SAVE_REFERENCE | 是否自動完成引用的 save 的相關操作 |
PRELOAD | 是否自動完成預加載的相關操作 |
主鍵、表名、列名的約定
主鍵(Primary Key)
GORM 默認會使用名為ID的字段作為表的主鍵。
type User struct {ID string // 名為`ID`的字段會默認作為表的主鍵Name string
}// 使用`AnimalID`作為主鍵
type Animal struct {AnimalID int64 `gorm:"primary_key"`Name stringAge int64
}
表名(Table Name)
表名默認就是結構體名稱的復數,例如:
type User struct {} // 默認表名是 `users`// 將 User 的表名設置為 `profiles`
func (User) TableName() string {return "profiles"
}func (u User) TableName() string {if u.Role == "admin" {return "admin_users"} else {return "users"}
}// 禁用默認表名的復數形式,如果置為 true,則 `User` 的默認表名是 `user`
db.SingularTable(true)
也可以通過Table()
指定表名:
// 使用User結構體創建名為`deleted_users`的表
db.Table("deleted_users").CreateTable(&User{})var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
SELECT * FROM deleted_users;db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
DELETE FROM deleted_users WHERE name = 'jinzhu';
GORM還支持更改默認表名稱規則:
gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {return "prefix_" + defaultTableName;
}
列名(Column Name)
列名由字段名稱進行下劃線分割來生成
type User struct {ID uint // column name is `id`Name string // column name is `name`Birthday time.Time // column name is `birthday`CreatedAt time.Time // column name is `created_at`
}
可以使用結構體tag指定列名:
type Animal struct {AnimalId int64 `gorm:"column:beast_id"` // set column name to `beast_id`Birthday time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`Age int64 `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}
時間戳跟蹤
CreatedAt
如果模型有 CreatedAt
字段,該字段的值將會是初次創建記錄的時間。
db.Create(&user) // `CreatedAt`將會是當前時間// 可以使用`Update`方法來改變`CreateAt`的值
db.Model(&user).Update("CreatedAt", time.Now())
UpdatedAt
如果模型有UpdatedAt
字段,該字段的值將會是每次更新記錄的時間。
db.Save(&user) // `UpdatedAt`將會是當前時間db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`將會是當前時間
DeletedAt
如果模型有DeletedAt
字段,調用Delete
刪除該記錄時,將會設置DeletedAt
字段為當前時間,而不是直接將記錄從數據庫中刪除。
CRUD接口
簡單的列舉了,創建、查詢、修改、刪除的使用,詳情可以看官方文檔。
創建 (Create)
// 創建單個記錄
user := User{Name: "張三", Age: 20}
result := db.Create(&user)
// 執行SQL: INSERT INTO users (name, age) VALUES ('張三', 20);// 批量創建
users := []User{{Name: "李四", Age: 22},{Name: "王五", Age: 23},
}
db.Create(&users)
// 執行SQL: INSERT INTO users (name, age) VALUES ('李四', 22), ('王五', 23);
查詢 (Read)
單條查詢
var user User
db.First(&user)
// 執行SQL: SELECT * FROM users ORDER BY id LIMIT 1;db.Where("name = ?", "張三").First(&user)
// 執行SQL: SELECT * FROM users WHERE name = '張三' ORDER BY id LIMIT 1;db.First(&user, 10)
// 執行SQL: SELECT * FROM users WHERE id = 10;
多條查詢
var users []User
db.Where("age > ?", 20).Find(&users)
// 執行SQL: SELECT * FROM users WHERE age > 20;db.Where(map[string]interface{}{"name": "張三", "age": 20}).Find(&users)
// 執行SQL: SELECT * FROM users WHERE name = '張三' AND age = 20;
高級查詢
db.Select("name", "age").Find(&users)
// 執行SQL: SELECT name, age FROM users;db.Order("age desc").Find(&users)
// 執行SQL: SELECT * FROM users ORDER BY age DESC;db.Limit(10).Offset(5).Find(&users)
// 執行SQL: SELECT * FROM users LIMIT 10 OFFSET 5;var count int64
db.Model(&User{}).Where("age > ?", 20).Count(&count)
// 執行SQL: SELECT COUNT(*) FROM users WHERE age > 20;
更新 (Update)
db.Save(&user)
// 執行SQL: UPDATE users SET name='張三', age=20 WHERE id=1;db.Model(&user).Update("name", "李四")
// 執行SQL: UPDATE users SET name='李四' WHERE id=1;db.Model(&user).Updates(User{Name: "李四", Age: 21})
// 執行SQL: UPDATE users SET name='李四', age=21 WHERE id=1;db.Model(&User{}).Where("age < ?", 20).Update("name", "未成年人")
// 執行SQL: UPDATE users SET name='未成年人' WHERE age < 20;
刪除 (Delete)
db.Delete(&user)
// 執行SQL: DELETE FROM users WHERE id=1;db.Delete(&User{}, 10)
// 執行SQL: DELETE FROM users WHERE id=10;db.Where("age < ?", 20).Delete(&User{})
// 執行SQL: DELETE FROM users WHERE age < 20;
事務
自動事務
db.Transaction(func(tx *gorm.DB) error {if err := tx.Create(&user1).Error; err != nil {return err}// 執行SQL: INSERT INTO users (name, age) VALUES ('user1', 20);if err := tx.Create(&user2).Error; err != nil {return err}// 執行SQL: INSERT INTO users (name, age) VALUES ('user2', 22);return nil
})
// 如果成功執行SQL: COMMIT;
// 如果失敗執行SQL: ROLLBACK;
手動事務
tx := db.Begin()
// 執行SQL: BEGIN;tx.Create(&user1)
// 執行SQL: INSERT INTO users (name, age) VALUES ('user1', 20);tx.Create(&user2)
// 執行SQL: INSERT INTO users (name, age) VALUES ('user2', 22);tx.Commit()
// 執行SQL: COMMIT;// 或者出錯時
tx.Rollback()
// 執行SQL: ROLLBACK;
嵌套事務
db.Transaction(func(tx *gorm.DB) error {tx.Create(&user1)// 執行SQL: INSERT INTO users (name, age) VALUES ('user1', 20);tx.Transaction(func(tx2 *gorm.DB) error {tx2.Create(&user2)// 執行SQL: SAVEPOINT sp1;// 執行SQL: INSERT INTO users (name, age) VALUES ('user2', 22);return errors.New("inner error")// 執行SQL: ROLLBACK TO sp1;})return nil// 執行SQL: COMMIT;
})
保存點 (SavePoint)
tx := db.Begin()
// 執行SQL: BEGIN;tx.Create(&user1)
// 執行SQL: INSERT INTO users (name, age) VALUES ('user1', 20);tx.SavePoint("sp1")
// 執行SQL: SAVEPOINT sp1;tx.Create(&user2)
// 執行SQL: INSERT INTO users (name, age) VALUES ('user2', 22);tx.RollbackTo("sp1")
// 執行SQL: ROLLBACK TO sp1;tx.Commit()
// 執行SQL: COMMIT;
參考資料:
GORM 指南
Golang 中文學習文檔 - 第三方庫 - GORM
李文周的博客 - GORM入門指南
李文周的博客 - GORM CRUD指南