「数据库表谁建的?Model 怎么来的?整个流程是什么?」—— 这篇把表与 Model 的关系、goctl 两条生成路线、生成的文件清单、接入 svc 再到在 logic 里用,一条龙讲清。
go-zero 不负责帮你建库建表——它只负责「读了表结构之后,自动生成一套数据访问代码(Model)」。所以流程是:你先准备表(手写 DDL 或连已有库)→ 跑 goctl model → 得到 model 包 → 注入 svc → 在 logic 里调。
记得住的一句话:建表是 DBA / 迁移工具 / 你自己执行 SQL 的事;写 Model 是 goctl 的事。go-zero 的 model = 基于 sqlx 封装的数据访问层(可选 Redis 缓存),只生成代码、不执行建表语句。
这一点最容易被误会成「goctl 会自动建表」。其实 goctl 只做代码生成,表本身得先存在(或先写好 DDL 文件)。
model 包(CRUD 代码 + 结构体 + 缓存逻辑)。CREATE TABLE 帮你把表落到库里;不会做数据库迁移(migration)。goctl model mysql datasource 连空库,会报错「表不存在」。先建表,再生成。建表方式任选其一:手写 .sql 在 MySQL 客户端执行、用 Flyway / golang-migrate 等迁移工具、或 DBA 在库里建好。
四步主线:准备表 → 生成 Model → 注入 svc → 在 logic 使用。
goctl model mysql ddl适合:你手头有一份建表 SQL,或想「表结构即文档」,从 SQL 文件生成。
Step 1 · 写建表 SQL(user.sql)
CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL DEFAULT '', age INT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_name (name) -- 二级索引会被识别,生成 FindOneByName ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Step 2 · 真正把表建进库(你执行,不是 goctl)
mysql -uroot -p test < user.sql # 用客户端 / 迁移工具执行
Step 3 · 生成 Model
goctl model mysql ddl \ --src user.sql \ # 一个或多个 .sql 文件 --dir ./model \ # 输出目录 --cache \ # 开启 Redis 缓存(可选) --style gozero # 代码风格(默认 gozero)
FindOne/Insert/Update/Delete;每个二级索引 KEY idx_xxx(col) → 额外生成 FindOneByXxx。索引写得越全,自带查询越多。goctl model mysql datasource适合:表已经在数据库里了(别人建的 / 老项目),直接反查结构生成代码,连 SQL 文件都不用写。
goctl model mysql datasource \ --url "root:password@tcp(127.0.0.1:3306)/test" \ --table user \ # 指定表,可多次 --table 或 --tables "a,b" --dir ./model \ --cache # 开启 Redis 缓存
.sql 文件。好处:表结构可纳入版本管理,先有 SQL 再有代码。model/ 包。区别只在于「信息源」是 SQL 文件还是数据库。执行完命令,./model 下会冒出这样一批文件。理解它们角色,才不会手改错文件。
usermodel_gen.gousermodel.goUserModel 接口,并声明 customUserModel 内嵌 defaultUserModel;你写的自定义查询方法放这里types.godb:"create_time" 标签vars.gocacheUserPrefix 等errors.goErrNotFoundtypes.go 长这样(列 → 字段映射)
type User struct { Id int64 `db:"id"` Name string `db:"name"` Age int64 `db:"age"` CreateTime time.Time `db:"create_time"` UpdateTime time.Time `db:"update_time"` }
db 标签;改列名后重新生成即可,不要手改这个结构体。和 DB 连接、Redis 一样,Model 也是「进程级共享依赖」,在 svc.NewServiceContext 里建一次、注入 svc。
① config.go 加两行
type Config struct { rest.RestConf Mysql sqlx.MysqlConf // 数据源 CacheRedis redis.RedisConf // 开 --cache 才需要 }
② svc/servicecontext.go 建 Model
type ServiceContext struct { Config config.Config UserModel model.UserModel // 注入 Model } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) // 一个连接池 return &ServiceContext{ Config: c, UserModel: model.NewUserModel(conn, c.CacheRedis), // 传连接 + 缓存 } }
sqlx.NewMysql 连接池可以被多个 Model 共用(NewUserModel(conn, ...)、NewOrderModel(conn, ...) 都传同一个 conn),连接只建一份。这正是 go-zero 把依赖都收编进 svc 的好处——见「svc 目录」「初始化」「全局变量」几篇。Model 注入 svc 后,任意 logic 通过 l.svcCtx.UserModel 使用,自带 context 透传与(若开 cache)缓存。
// 查 user, err := l.svcCtx.UserModel.FindOne(l.ctx, userId) if errors.Is(err, model.ErrNotFound) { return nil, err } // 按索引查(建表时写了 KEY idx_name 才有) u, err := l.svcCtx.UserModel.FindOneByName(l.ctx, name) // 增 _, err = l.svcCtx.UserModel.Insert(l.ctx, &model.User{Name: "张三", Age: 12}) // 改 user.Age = 13 l.svcCtx.UserModel.Update(l.ctx, user) // 删 l.svcCtx.UserModel.Delete(l.ctx, user.Id)
整个链条回顾:表(你建)→ goctl 生成 model → svc 注入 UserModel → logic 里 l.svcCtx.UserModel.FindOne(...)。go-zero 帮你省掉的是:手写 SQL、手写 struct 映射、手写增删改查、手写缓存——这些全在 _gen.go 里自动完成。
聚合查询、连表、复杂 WHERE —— goctl 生成不了,但你可以在 非 _gen 文件里扩展,重生成不会被覆盖。
// 1) 在 usermodel.go 的 UserModel 接口加签名 UserModel interface { model.UserModel // 内嵌生成的基础接口 FindAdults(ctx context.Context) ([]*User, error) // 自定义 } // 2) 在同一文件的 customUserModel 上实现(手写,不会被覆盖) func (m *customUserModel) FindAdults(ctx context.Context) ([]*User, error) { query := `SELECT `+userRows+` FROM `+m.table+` WHERE age >= ?` var resp []*User err := m.conn.QueryRowsCtx(ctx, &resp, query, 18) return resp, err }
usermodel_gen.go——那个文件下次 goctl model 会被整体覆盖,你的代码瞬间消失。约定:生成的写 _gen.go,手写的写 usermodel.go。加 --cache 后,goctl 把主键 / 唯一索引查询的 Redis 缓存逻辑直接写进 _gen.go:查先打缓存、命中即返;写操作自动删对应缓存。
vars.go 里按 cacheUserPrefix + 主键拼。CacheRedis redis.RedisConf(连 Redis 的地址),否则启动报错。_gen.gousermodel.goKEY idx_xxxCacheRedis 并配地址目标都是「表 ↔ 代码」对齐,机制差异很大。
goctl model 读表/DDL 生成 sqlx 代码;不建表db.AutoMigrate(&User{}) 自动建/改表engine.Sync(&User{}) 同步表建表是「你」的事(DDL / 迁移工具)→ Model 是 goctl model mysql ddl|datasource 从表或 SQL 生成的事(只产代码,不落表)→ 接入是 svc 里 NewXxxModel(conn, cache) 的事 → 使用是 logic 里 l.svcCtx.XxxModel.FindOne(...) 的事。
ddl(从 SQL 文件)/ datasource(从活库),产物相同。usermodel.go,别碰 _gen.go。--cache 自动管主键/唯一索引缓存,自定义查询自管。