GORM(Go ORM)是 Go 语言最流行的对象关系映射库,提供模型定义、CRUD 操作、关联查询、事务处理等完整功能。本文将深入探讨 GORM 的实战应用和最佳实践。
一、GORM 简介
1.1 核心特性
| 特性 | 说明 |
|---|---|
| 全功能 ORM | 模型定义、CRUD、关联、事务 |
| 自动迁移 | 根据模型自动创建/更新表结构 |
| 钩子函数 | Before/After Save/Update/Delete |
| 预加载 | 优雅解决 N+1 查询问题 |
| 多数据库 | MySQL、PostgreSQL、SQLite、SQLServer |
1.2 安装与配置
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
连接数据库:
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic("failed to connect database")
}
// 连接池配置
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
}
二、模型定义
2.1 基础模型
package model
import (
"time"
"gorm.io/gorm"
)
// User 用户模型
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 字段定义
Username string `gorm:"size:50;uniqueIndex;not null" json:"username"`
Password string `gorm:"size:100;not null" json:"-"`
Email string `gorm:"size:100;index" json:"email"`
Age int `gorm:"comment:年龄" json:"age"`
Status int `gorm:"default:1" json:"status"`
// 关联
Profile *Profile `json:"profile"`
Orders []Order `json:"orders"`
Roles []Role `gorm:"many2many:user_roles;" json:"roles"`
}
// Profile 用户档案
type Profile struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"uniqueIndex;not null"`
Avatar string `gorm:"size:255"`
Bio string `gorm:"type:text"`
Website string `gorm:"size:255"`
}
// Order 订单
type Order struct {
ID uint `gorm:"primaryKey"`
OrderNo string `gorm:"size:50;uniqueIndex;not null"`
UserID uint `gorm:"index;not null"`
Amount float64 `gorm:"type:decimal(10,2);not null"`
Status int `gorm:"default:0"`
User *User `json:"user,omitempty"`
Items []OrderItem `json:"items"`
}
// OrderItem 订单项
type OrderItem struct {
ID uint `gorm:"primaryKey"`
OrderID uint `gorm:"index;not null"`
ProductID uint `gorm:"index;not null"`
Quantity int `gorm:"not null"`
Price float64 `gorm:"type:decimal(10,2);not null"`
}
// Role 角色
type Role struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:50;uniqueIndex;not null"`
Users []User `gorm:"many2many:user_roles;"`
}
2.2 高级模型特性
type Product struct {
ID uint `gorm:"primaryKey"`
Code string `gorm:"uniqueIndex;size:20"`
Price float64 `gorm:"type:decimal(10,2)"`
// 忽略字段
Ignored string `gorm:"-"`
// 默认值
Status int `gorm:"default:1"`
// 自动创建时间
CreatedAt time.Time
// 指定表名
gorm.Model
}
// 自定义表名
func (Product) TableName() string {
return "products"
}
三、CRUD 操作
3.1 创建数据
// 单条创建
user := User{Username: "张三", Email: "zhangsan@example.com", Age: 25}
db.Create(&user)
// 批量创建
users := []User{
{Username: "李四", Email: "lisi@example.com"},
{Username: "王五", Email: "wangwu@example.com"},
}
db.Create(&users)
// 指定字段创建
db.Select("Username", "Email").Create(&user)
// 忽略字段创建
db.Omit("Status").Create(&user)
3.2 查询数据
var user User
var users []User
// 根据主键查询
db.First(&user, 1)
db.Take(&user, 1)
// 条件查询
db.Where("username = ?", "张三").First(&user)
db.Where("age > ? AND status = ?", 18, 1).Find(&users)
// Map 条件
db.Where(map[string]interface{}{"username": "张三", "status": 1}).Find(&users)
// IN 查询
db.Where("username IN ?", []string{"张三", "李四"}).Find(&users)
// 模糊查询
db.Where("username LIKE ?", "%张%").Find(&users)
// 获取所有
db.Find(&users)
// 限制和偏移
db.Limit(10).Offset(20).Find(&users)
// 排序
db.Order("age desc, username asc").Find(&users)
// 选择字段
db.Select("username", "email").Find(&users)
3.3 更新数据
// 更新单个字段
db.Model(&user).Update("Age", 26)
// 更新多个字段
db.Model(&user).Updates(map[string]interface{}{
"Age": 26,
"Status": 1,
})
// 使用结构体更新(只更新非零值)
db.Model(&user).Updates(User{Age: 26, Status: 1})
// 更新所有字段(包括零值)
db.Model(&user).Select("*").Updates(map[string]interface{}{
"Age": 0,
"Status": 0,
})
3.4 删除数据
// 软删除(需要模型有 DeletedAt 字段)
db.Delete(&user)
// 硬删除
db.Unscoped().Delete(&user)
// 条件删除
db.Where("status = ?", 0).Delete(&User{})
// 批量删除
db.Delete(&users)
四、关联查询
4.1 一对一
// 创建用户和档案
user := User{
Username: "张三",
Profile: &Profile{
Avatar: "avatar.jpg",
Bio: "简介",
Website: "https://example.com",
},
}
db.Create(&user)
// 预加载档案
var user User
db.Preload("Profile").First(&user, 1)
4.2 一对多
// 创建用户和订单
user := User{
Username: "张三",
Orders: []Order{
{OrderNo: "ORD001", Amount: 100},
{OrderNo: "ORD002", Amount: 200},
},
}
db.Create(&user)
// 预加载订单
var user User
db.Preload("Orders").First(&user, 1)
// 条件预加载
db.Preload("Orders", "status = ?", 1).First(&user, 1)
// 嵌套预加载
db.Preload("Orders.Items").First(&user, 1)
4.3 多对多
// 创建用户和角色
user := User{
Username: "张三",
Roles: []Role{
{Name: "admin"},
{Name: "user"},
},
}
db.Create(&user)
// 预加载角色
var user User
db.Preload("Roles").First(&user, 1)
// 关联查询
var roles []Role
db.Model(&user).Where("name = ?", "admin").Association("Roles").Find(&roles)
// 添加关联
db.Model(&user).Association("Roles").Append(&Role{Name: "vip"})
// 删除关联
db.Model(&user).Association("Roles").Delete(&adminRole)
// 替换关联
db.Model(&user).Association("Roles").Replace(&[]Role{
{Name: "user"},
{Name: "vip"},
})
4.4 解决 N+1 问题
// ❌ N+1 查询
var users []User
db.Find(&users)
for i := range users {
var profile Profile
db.Where("user_id = ?", users[i].ID).First(&profile)
}
// ✅ 使用预加载
var users []User
db.Preload("Profile").Find(&users)
五、事务处理
5.1 基础事务
func TransferMoney(db *gorm.DB, fromID, toID uint, amount float64) error {
// 开启事务
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 扣款
if err := tx.Model(&User{}).Where("id = ?", fromID).
Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
tx.Rollback()
return err
}
// 收款
if err := tx.Model(&User{}).Where("id = ?", toID).
Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
return tx.Commit().Error
}
5.2 事务选项
// 使用 WithContext
tx := db.WithContext(ctx).Begin()
// 设置隔离级别
tx = db.Session(&gorm.Session{
PrepareStmt: true,
}).Begin()
// 保存点
tx.SavePoint("save1")
tx.RollbackTo("save1")
六、钩子函数
type User struct {
ID uint
Username string
Password string
Email string
}
// 创建前钩子
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 密码加密
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
u.Password = string(hashedPassword)
return nil
}
// 更新前钩子
func (u *User) BeforeUpdate(tx *gorm.DB) error {
// 验证邮箱
if !isValidEmail(u.Email) {
return errors.New("invalid email")
}
return nil
}
// 删除后钩子
func (u *User) AfterDelete(tx *gorm.DB) error {
// 清理关联
tx.Where("user_id = ?", u.ID).Delete(&Profile{})
return nil
}
七、性能优化
7.1 索引优化
type Product struct {
ID uint `gorm:"primaryKey"`
Code string `gorm:"uniqueIndex;size:20"`
Category string `gorm:"index"`
Price float64 `gorm:"type:decimal(10,2);index"`
}
7.2 批量操作
// ❌ 低效
for _, user := range users {
db.Create(&user)
}
// ✅ 高效
db.Create(&users)
7.3 选择字段
// 只查询需要的字段
db.Select("id", "username", "email").Find(&users)
// 排除大字段
db.Omit("bio", "avatar").Find(&users)
7.4 禁用钩子
// 批量导入时禁用钩子提升性能
db.Session(&gorm.Session{SkipHooks: true}).Create(&users)
八、实战案例
8.1 分页查询
func Paginate[T any](db *gorm.DB, page, pageSize int) *gorm.DB {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 10
}
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
// 使用
var users []User
var total int64
db.Model(&User{}).Count(&total)
Paginate[User](db, 1, 10).Find(&users)
8.2 软删除查询
// 查询未删除的数据
db.Where("deleted_at IS NULL").Find(&users)
// 查询包括已删除的数据
db.Unscoped().Find(&users)
// 恢复已删除的数据
db.Unscoped().Where("id = ?", 1).Update("deleted_at", nil)
8.3 动态查询
type UserQuery struct {
Username string `form:"username"`
Email string `form:"email"`
MinAge int `form:"min_age"`
MaxAge int `form:"max_age"`
}
func SearchUsers(db *gorm.DB, query UserQuery) ([]User, error) {
var users []User
tx := db.Model(&User{})
if query.Username != "" {
tx = tx.Where("username LIKE ?", "%"+query.Username+"%")
}
if query.Email != "" {
tx = tx.Where("email LIKE ?", "%"+query.Email+"%")
}
if query.MinAge > 0 {
tx = tx.Where("age >= ?", query.MinAge)
}
if query.MaxAge > 0 {
tx = tx.Where("age <= ?", query.MaxAge)
}
err := tx.Find(&users).Error
return users, err
}
九、常见问题
9.1 字段为零值不更新
// ❌ 问题:Age=0 不会更新
db.Model(&user).Updates(User{Age: 0, Status: 1})
// ✅ 解决方案 1:使用 Map
db.Model(&user).Updates(map[string]interface{}{"Age": 0, "Status": 1})
// ✅ 解决方案 2:Select 所有字段
db.Model(&user).Select("*").Updates(User{Age: 0, Status: 1})
9.2 关联不自动保存
// ❌ 问题:关联数据不会自动保存
user := User{Username: "张三", Profile: &Profile{Bio: "简介"}}
db.Create(&user) // Profile 不会保存
// ✅ 解决方案:显式创建
db.Create(&user.Profile)
十、总结
GORM 最佳实践
| 实践 | 说明 |
|---|---|
| 使用预加载 | 避免 N+1 查询 |
| 批量操作 | 提升写入性能 |
| 事务处理 | 保证数据一致性 |
| 索引优化 | 提升查询性能 |
| 选择字段 | 减少数据传输 |
参考资料
- GORM 官方文档
- GORM GitHub
- 《Go 语言高级编程》- 柴树杉