GORM Gen还是太全面了
·
目录
一、GORM Gen 工具生成代码的步骤(保姆级教程)
1. 安装 GORM Gen 工具
# 安装命令行工具
go get -u gorm.io/gen
go get -u gorm.io/gen/field
2. 准备模型文件(示例:model/user.go)
package model
// 用户表
type User struct {
ID int64 `gorm:"primaryKey;autoIncrement"` // 主键自增
Username string `gorm:"uniqueIndex;not null"` // 唯一索引,非空
Password string `gorm:"not null"` // 密码(加密存储)
Nickname string `gorm:"size:50"` // 昵称,最大长度50
Email string `gorm:"uniqueIndex"` // 邮箱唯一索引
Mobile string `gorm:"index"` // 手机号普通索引
Avatar string `gorm:"default:default_avatar.png"` // 默认头像
Status int32 `gorm:"default:1"` // 状态,默认正常(1)
CreatedAt Time `gorm:"autoCreateTime"` // 创建时间自动填充
UpdatedAt Time `gorm:"autoUpdateTime"` // 更新时间自动更新
}
// 自定义时间类型(可选,用于适配数据库时区等)
type Time struct {
time.Time
}
3. 编写生成配置文件(gen/dao_gen.go)
package main
import (
"awesomeProject/internal/model" // 引入模型包
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
)
func main() {
// 连接数据库(测试环境,生成代码后建议关闭)
db, err := gorm.Open(mysql.Open("root:password@tcp(127.0.0.1:3306/test_db?charset=utf8mb4"))
if err != nil {
panic("数据库连接失败: " + err.Error())
}
// 初始化代码生成器
g := gen.NewGenerator(gen.Config{
OutPath: "./internal/dao", // 生成代码输出路径
ModelPkgPath: "awesomeProject/internal/model", // 模型包路径
Mode: gen.WithDefaultQuery | gen.WithQueryInterface, // 生成默认查询和接口
})
// 指定生成的表和字段
user := g.GenerateModel(&model.User{}, gen.FieldCover{
ID: field.NewInt64("id"), // 用户ID
Username: field.NewString("username"), // 用户名
// 其他字段...
})
// 配置查询结构体(可选,用于自定义查询方法)
g.ApplyBasic(user) // 生成基础CRUD方法
g.ApplyInterface(func(methods gen.Methods) gen.Methods {
// 自定义扩展方法(如分页查询)
return methods
})
// 执行生成
g.Execute()
}
4. 执行生成命令
go run gen/dao_gen.go
生成文件说明
- 生成的代码位于
./internal/dao目录 - 核心文件:
user_gen.go(包含用户表的查询构建器)
二、生成代码逐段解析(以用户表为例)
1. 包声明与依赖引入
package dao // 代码属于数据访问层(DAO)
import (
"context" // 上下文,用于数据库操作的上下文管理
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field" // 字段操作工具包
"gorm.io/plugin/dbresolver" // 读写分离插件
"awesomeProject/internal/model" // 自定义业务模型
)
- 作用:
- 引入 GORM 核心库和代码生成相关包
- 关联业务模型
model.User - 支持读写分离(
dbresolver)和字段表达式(field)
2. 生成器入口函数 newUser
func newUser(db *gorm.DB, opts ...gen.DOOption) user {
_user := user{}
_user.userDo.UseDB(db, opts...) // 绑定数据库连接
_user.userDo.UseModel(&model.User{}) // 绑定业务模型
tableName := _user.userDo.TableName() // 获取表名(默认与模型名一致,如"users")
// 初始化字段映射(将结构体字段与数据库列名绑定)
_user.ALL = field.NewAsterisk(tableName) // 通配符(SELECT *)
_user.ID = field.NewInt64(tableName, "id") // 整数类型字段(对应数据库BIGINT)
_user.Username = field.NewString(tableName, "username") // 字符串类型字段
// ...其他字段初始化类似...
_user.fillFieldMap() // 填充字段映射表(用于动态查询)
return _user
}
- 场景:
- 当需要操作用户表时,通过
newUser(db)创建查询对象 - 自动绑定业务模型
model.User和数据库表 - 大白话:相当于给用户表创建一个 “操作手柄”,后续增删改查都通过这个手柄进行
- 当需要操作用户表时,通过
3. user结构体定义
// user 用户表
type user struct {
userDo userDo // 内嵌GORM生成的基础操作结构体
// 以下是数据库表字段的Go语言映射
ALL field.Asterisk // 代表所有字段(*)
ID field.Int64 // 用户ID(对应数据库id列)
Username field.String // 用户名(对应username列)
// ...其他字段...
fieldMap map[string]field.Expr // 字段名映射表(用于通过字符串动态获取字段表达式)
}
- 核心设计:
userDo:封装了 GORM 原生的DO结构体,提供基础 CRUD 方法fieldMap:将数据库列名(如 "username")映射到 Go 字段(如u.Username),方便动态构建查询- 场景:当需要动态拼接 SQL 条件(如根据用户输入的字段名排序)时,可通过
fieldMap获取字段表达式
4. 表名与别名操作方法
func (u user) Table(newTableName string) *user {
u.userDo.UseTable(newTableName) // 修改表名(如操作分表users_2023)
return u.updateTableName(newTableName) // 更新字段对应的表名
}
func (u user) As(alias string) *user {
u.userDo.DO = *(u.userDo.As(alias).(*gen.DO)) // 设置表别名(如SELECT u.id FROM users AS u)
return u.updateTableName(alias)
}
- 场景:
- 分表操作:当用户表按年份分表时,通过
Table("users_2024")切换操作的表 - 复杂查询:给表起别名(如
As("u")),方便 JOIN 操作时引用字段
- 分表操作:当用户表按年份分表时,通过
5. 字段映射填充方法 fillFieldMap
func (u *user) fillFieldMap() {
u.fieldMap = make(map[string]field.Expr, 10)
u.fieldMap["id"] = u.ID
u.fieldMap["username"] = u.Username
// ...其他字段一一映射...
}
- 作用:建立字段名(字符串)与 Go 字段对象的映射关系
- 场景:当需要根据用户传入的字段名动态排序或筛选时,直接从
fieldMap获取字段表达式func OrderByField(fieldName string) { if f, ok := u.fieldMap[fieldName]; ok { u.Order(f) // 动态排序 } }
6. userDo结构体(基础操作封装)
type userDo struct{ gen.DO } // 继承自gen.DO,拥有GORM的所有查询能力
// 以下是常用查询方法的封装(基于GORM原生方法)
func (u userDo) Debug() *userDo { return u.withDO(u.DO.Debug()) } // 开启SQL调试日志
func (u userDo) Where(conds ...gen.Condition) *userDo { return u.withDO(u.DO.Where(conds...)) } // 条件查询
func (u userDo) Create(values ...*model.User) error { /*...*/ } // 批量创建
func (u userDo) Find() ([]*model.User, error) { /*...*/ } // 查询所有
- 场景:
- 条件查询:
u.Where(u.Username.Eq("admin"))相当于WHERE username = 'admin' - 批量插入:
u.CreateInBatches(users, 100)一次性插入 100 条数据,提升性能 - 读写分离:
u.ReadDB().Find()使用从库查询,u.WriteDB().Create()使用主库写入
- 条件查询:
7. 分页查询方法 FindByPage
func (u userDo) FindByPage(offset int, limit int) (result []*model.User, count int64, err error) {
result, err = u.Offset(offset).Limit(limit).Find() // 执行分页查询
if err != nil {
return
}
// 优化:当查询结果不足一页时,直接返回实际数量
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
// 否则单独查询总记录数
count, err = u.Offset(-1).Limit(-1).Count() // Offset(-1)取消分页,获取总数
return
}
- 场景:
- 前端分页展示用户列表时,直接调用
FindByPage(0, 20)获取第 1 页 20 条数据 - 优化点:当查询结果不足一页时,避免额外执行
COUNT(*),提升性能
- 前端分页展示用户列表时,直接调用
三、生成代码的核心优势(为什么比手动写更好?)
1. 类型安全与自动补全
- 字段操作通过结构体字段(如
u.Username)而非字符串,避免 SQL 注入和拼写错误 - IDE 可自动识别字段类型,提供补全和校验(如
u.Username是field.String,可调用.Eq()、.Like()等方法)
2. 动态查询与灵活组合
// 动态拼接查询条件
var conditions []gen.Condition
if username != "" {
conditions = append(conditions, u.Username.Like("%"+username+"%")) // 模糊查询用户名
}
if status != 0 {
conditions = append(conditions, u.Status.Eq(status)) // 精确查询状态
}
users, err := u.Where(conditions...).Order(u.CreatedAt.Desc()).Find() // 组合条件+排序
3. 内置常用功能
- 读写分离:通过
ReadDB()/WriteDB()自动路由到不同数据库 - 批量操作:
CreateInBatches、FindInBatches处理大数据量场景 - 事务支持:结合 GORM 原生
Transaction方法使用err := db.Transaction(func(tx *gorm.DB) error { dao := newUser(tx) // 使用事务中的数据库连接 return dao.Create(user) })
4. 业务逻辑与数据访问解耦
- 生成的代码专注于数据访问(DAO 层),业务逻辑(如用户注册、登录)可在服务层(Service)中调用
- 示例分层架构:
// 服务层 func CreateUserService(ctx context.Context, username string, password string) error { user := &model.User{ Username: username, Password: encrypt(password), // 业务层处理密码加密 } return dao.NewUser(db).WithContext(ctx).Create(user) // DAO层仅负责数据库操作 }
四、实际场景应用示例
场景 1:用户注册(插入数据)
func RegisterUser(ctx context.Context, username, password, email string) error {
user := &model.User{
Username: username,
Password: password, // 实际应用中需加密
Email: email,
Status: 1, // 正常状态
}
// 使用生成的Create方法插入数据
return dao.NewUser(db).WithContext(ctx).Create(user)
}
场景 2:根据用户名查询用户(带条件)
func GetUserByUsername(ctx context.Context, username string) (*model.User, error) {
userDO := dao.NewUser(db).WithContext(ctx)
// 使用Username字段构建查询条件
return userDO.Where(userDO.Username.Eq(username)).First()
}
场景 3:分页查询用户列表(带排序)
func ListUsers(ctx context.Context, page, pageSize int, orderField string) ([]*model.User, int64, error) {
userDO := dao.NewUser(db).WithContext(ctx)
// 动态排序:从fieldMap中获取字段表达式
if f, ok := userDO.fieldMap[orderField]; ok {
userDO = userDO.Order(f.Desc()) // 降序排序
} else {
// 默认按创建时间排序
userDO = userDO.Order(userDO.CreatedAt.Desc())
}
// 调用分页方法
return userDO.FindByPage((page-1)*pageSize, pageSize)
}
场景 4:批量软删除用户(更新状态)
func BatchDisableUsers(ctx context.Context, userIDs []int64) (int64, error) {
userDO := dao.NewUser(db).WithContext(ctx)
result, err := userDO.
Where(userDO.ID.In(userIDs)). // 条件:ID在列表中
Assign(userDO.Status, 0). // 设置状态为禁用(0)
Update() // 执行更新
if err != nil {
return 0, err
}
return result.RowsAffected, nil // 返回影响的行数
}
五、扩展技巧:自定义查询方法
需求:查询活跃用户(状态正常且最近 30 天登录过)
// 在生成的user结构体中添加自定义方法
func (u user) FindActiveUsers(lastLoginDays int) ([]*model.User, error) {
return u.Where(
u.Status.Eq(1), // 状态正常
u.UpdatedAt.Gt(time.Now().AddDate(0, 0, -lastLoginDays)), // 最近30天更新过
).Find()
}
// 使用示例
activeUsers, err := dao.NewUser(db).FindActiveUsers(30)
需求:统计各状态用户数量(分组查询)
func CountUsersByStatus(ctx context.Context) (map[int32]int64, error) {
var result []struct {
Status int32 `gorm:"status"`
Count int64 `gorm:"count"`
}
err := dao.NewUser(db).WithContext(ctx).
Select(u.Status, field.Count("*").As("count")). // 选择状态和计数
Group(u.Status). // 按状态分组
Scan(&result)
if err != nil {
return nil, err
}
// 转换为map
countMap := make(map[int32]int64, len(result))
for _, r := range result {
countMap[r.Status] = r.Count
}
return countMap, nil
}
六、注意事项
- 模型变更后重新生成:当
model.User字段变更时,需重新运行生成工具 - 避免直接修改生成代码:生成的代码会被覆盖,扩展功能请通过接口或组合方式实现
- 性能调优:复杂查询可结合
EXPLAIN分析执行计划,确保索引有效使用 - 事务与上下文:在高并发场景下,建议通过
WithContext(ctx)传递请求上下文,避免 goroutine 泄漏
通过 GORM Gen 工具,开发者只需专注于业务模型定义,数据访问层代码由工具自动生成,大幅提升开发效率的同时保证代码质量。生成的代码兼具类型安全、灵活扩展和性能优化等特性,尤其适合中大型项目的数据库操作场景。
更多推荐


所有评论(0)