目录

一、GORM Gen 工具生成代码的步骤(保姆级教程)

1. 安装 GORM Gen 工具

2. 准备模型文件(示例:model/user.go)

3. 编写生成配置文件(gen/dao_gen.go)

4. 执行生成命令

生成文件说明

二、生成代码逐段解析(以用户表为例)

1. 包声明与依赖引入

2. 生成器入口函数 newUser

3. user结构体定义

4. 表名与别名操作方法

5. 字段映射填充方法 fillFieldMap

6. userDo结构体(基础操作封装)

7. 分页查询方法 FindByPage

三、生成代码的核心优势(为什么比手动写更好?)

1. 类型安全与自动补全

2. 动态查询与灵活组合

3. 内置常用功能

4. 业务逻辑与数据访问解耦

四、实际场景应用示例

场景 1:用户注册(插入数据)

场景 2:根据用户名查询用户(带条件)

场景 3:分页查询用户列表(带排序)

场景 4:批量软删除用户(更新状态)

五、扩展技巧:自定义查询方法

需求:查询活跃用户(状态正常且最近 30 天登录过)

需求:统计各状态用户数量(分组查询)

六、注意事项


一、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.Usernamefield.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()自动路由到不同数据库
  • 批量操作CreateInBatchesFindInBatches处理大数据量场景
  • 事务支持:结合 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
}

六、注意事项

  1. 模型变更后重新生成:当model.User字段变更时,需重新运行生成工具
  2. 避免直接修改生成代码:生成的代码会被覆盖,扩展功能请通过接口或组合方式实现
  3. 性能调优:复杂查询可结合EXPLAIN分析执行计划,确保索引有效使用
  4. 事务与上下文:在高并发场景下,建议通过WithContext(ctx)传递请求上下文,避免 goroutine 泄漏

通过 GORM Gen 工具,开发者只需专注于业务模型定义,数据访问层代码由工具自动生成,大幅提升开发效率的同时保证代码质量。生成的代码兼具类型安全、灵活扩展和性能优化等特性,尤其适合中大型项目的数据库操作场景。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐