作为Go语言生态中最流行的ORM框架之一,GORM以其简洁的API和强大的功能赢得了广大开发者的青睐。在实际项目开发中,模型定义和基础查询是使用GORM最频繁的操作,也是构建复杂业务逻辑的基础。本文将结合我多年使用GORM的经验,深入剖析模型定义的最佳实践和基础查询的各种技巧。
GORM采用约定优于配置的原则,自动将Go结构体映射到数据库表。默认情况下,结构体名采用驼峰命名法,对应的表名是其复数形式的蛇形命名。例如:
go复制type UserAccount struct {} // 默认映射到user_accounts表
这种自动映射机制可以通过实现Tabler接口来覆盖:
go复制func (User) TableName() string {
return "custom_users"
}
GORM提供的gorm.Model结构体包含了四个常用字段:
go复制type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
在实际项目中,我推荐大多数场景下嵌入gorm.Model,原因如下:
注意:如果你的表主键不是自增整数,或者需要完全自定义字段名,则不适合嵌入gorm.Model
在某些特殊场景下,我们需要自定义主键和字段:
go复制type Product struct {
Code string `gorm:"primaryKey;size:50"`
Price uint
// 显式定义时间字段
CreatedTime time.Time `gorm:"column:create_time"`
UpdatedTime time.Time `gorm:"column:update_time"`
}
自定义字段时需要注意:
primaryKeycolumn标签指定不同的列名GORM提供了丰富的结构体标签来控制字段行为:
go复制type User struct {
gorm.Model
Name string `gorm:"size:100;not null"`
Age int `gorm:"default:18"`
Email string `gorm:"uniqueIndex;size:255"`
CreditCard string `gorm:"-:migration"` // 不生成数据库字段
ExternalData string `gorm:"serializer:json"` // 序列化为JSON存储
}
常用标签说明:
size:定义字段长度,特别是字符串类型not null:字段不允许为NULLdefault:设置字段默认值uniqueIndex:创建唯一索引index:创建普通索引-:忽略该字段serializer:指定序列化方式GORM的查询构建器采用链式调用方式,非常直观:
go复制// 查询所有用户
var users []User
result := db.Find(&users)
// 查询单个用户
var user User
db.First(&user, "id = ?", 1)
查询方法分类:
go复制// 字符串条件
db.Where("name = ?", "jinzhu").First(&user)
// 结构体条件
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// Map条件
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
go复制// IN查询
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// LIKE查询
db.Where("name LIKE ?", "%jin%").Find(&users)
// AND条件组合
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// 时间范围查询
today := time.Now()
db.Where("created_at BETWEEN ? AND ?", today.Add(-24*time.Hour), today).Find(&users)
这三个方法都用于查询单条记录,但有重要区别:
First:按主键升序获取第一条记录Last:按主键降序获取第一条记录Take:随机获取一条记录重要提示:当查询不到记录时,First和Last会返回ErrRecordNotFound错误,而Take不会
go复制var count int64
db.Model(&User{}).Where("age > ?", 20).Count(&count)
go复制type APIUser struct {
ID uint
Name string
}
db.Model(&User{}).Limit(10).Find(&APIUser{})
GORM内置了软删除支持,使用DeletedAt字段标记删除:
go复制// 软删除
db.Delete(&user)
// 查询时自动过滤已删除记录
db.Where("age = ?", 20).Find(&users)
// 查询包含已删除的记录
db.Unscoped().Where("age = ?", 20).Find(&users)
// 永久删除
db.Unscoped().Delete(&user)
go复制type User struct {
gorm.Model
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
// 预加载用户的订单
db.Preload("Orders").Find(&users)
go复制// 子查询作为条件
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// 子查询作为字段
db.Select("*, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) as order_count").Find(&users)
go复制db.Raw("SELECT id, name FROM users WHERE age > ?", 18).Scan(&result)
SELECT *go复制// 手动事务
tx := db.Begin()
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
// 自动事务
if err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err
}
return nil
}); err != nil {
// 处理错误
}
在实际项目中,我总结了以下经验:
对于大型项目,我建议:
GORM是一个非常强大的ORM框架,掌握好模型定义和基础查询是使用它的关键。通过合理的设计和优化,可以构建出既高效又易于维护的数据访问层。