上一篇:GORM GEN 快速开始
下一篇:GORM GEN的CRUD
推荐有时间的同学可以将系列文章都过一遍有个印象、没有时间的同学可以通过关键词搜索找到自己需要的内容。
前面介绍了GEN的基础,以及快速开始相关的内容,相信你已经了解了GEN的能力和最简单的使用。本文是GEN教程系列的第二篇,将详细介绍代码生成的能力,包括直接同步数据库表生成、基于已有的model生成以及通过建表SQL生成三部分。
同步数据库表生成
1.1 配置介绍
一下介绍的配置都是全局配置,对所有表都生效,主要包括生成的模式、生成的路径、类型和名称等配置。
// GenerateMode generate mode
type GenerateMode uint
const (
// 自动生成全局变量(推荐)
WithDefaultQuery GenerateMode = 1 << iota
// 不强制使用Context(公司内不推荐)
WithoutContext
// 为dao生成接口,有mock需求的可以用
WithQueryInterface
)
// Config generator's basic configuration
type Config struct {
db *gorm.DB // 需要从数据库同步的时候需要,同步g.UseDB(db)写入
OutPath string // 代码生成的路径
OutFile string // gen文件名 default: gen.go
ModelPkgPath string // 指定model的包,默认 model
WithUnitTest bool // 是否生成单元测试
// generate model global configuration
FieldNullable bool // 允许为null的字段生成指针类型
FieldCoverable bool // 存在非零默认值的字段生成指针类型, to fix problem zero value cannot be assign: https://gorm.io/docs/create.html#Default-Values
FieldSignable bool // 是否生成无符号类型(uint\uint32\uint64 等)
FieldWithIndexTag bool // 是否生成索引的tag(idx_xxx)
FieldWithTypeTag bool // 是否生成db类型tag(vachar(64))
Mode GenerateMode // 最前面的定义,指定多个 WithDefaultQuery|WithQueryInterface
}
//1. 全局可选项,对所有表都生效
WithOpts(opts ...ModelOpt)
// 比如指定字段类型,gen.FieldType("is_deleted","gorm.DeletedAt")
// g.WithOpts(gen.FieldType("deleted_at","gorm.DeletedAt"))
//2. 指定表名称策略
WithTableNameStrategy(ns func(tableName string) (targetTableName string))
//比如过滤某些表,不生成
/*
g.WithTableNameStrategy(func(tableName string) (targetTableName string) {
if strings.HasPrefix(tableName, "_") { //忽略下划线开头的表
return ""
}
return tableName
})
*/
//3. 指定表生成的model的struct的名称,默认是转大驼峰
WithModelNameStrategy(ns func(tableName string) (modelName string))
//4. 指定表生成的文件名称
WithFileNameStrategy(ns func(tableName string) (fileName string))
//5. db类型和go类型映射关系,默认映射不满足需求的时候可以用
WithDataTypeMap(newMap map[string]func(columnType gorm.ColumnType)
//比如 tinyint(1)不想生成bool而是int32类型
/*
g.WithDataTypeMap(map[string]func(columnType gorm.ColumnType) (dataType string){
"tinyint": func(columnType gorm.ColumnType) (dataType string) {
ct, _ := columnType.ColumnType()
if strings.HasPrefix(ct, "tinyint(1)") {
return "int8"
}
return "int16"
},
})
*/
//6. 指定生成的json tag的名称,默认是db定义的字段名
WithJSONTagNameStrategy(ns func(columnName string) (tagContent string))
//7. 指定自定义类型/也可以是类型冲突时自定义
WithImportPkgPath(paths ...string)
//比如依赖了一个其他包的model
// g.WithImportPkgPath("xxmodel xxx.xxx.model")
1.2 可选项介绍
这部分主要是model生成的一些可选项,用于一些表字段的特殊需求,比如某个字段需要生成自定义类型、需要修改生成的Field、需要修改字段tag、需要忽略些字段、新增一些非DB字段以及生成关联关系等。这部分选项一般是对某个表的字段做修改,所以是作用在GenerateModel/GenerateModelAs上的,当然也可能有需求要适用所有的表,比如忽略下划线开头的字段或者是删除字段统一用gorm的软删除等等,这种全局生效的场景可以用前面配置提到的WithOpts。
//1. 通用的选项,允许你对整个字段进行修改(名字、类型、标签、关联关系等)
//下面其他的选项都是对这个封装/简化
FieldModify
//比如针对id字段,删除default标签
/*
g.GenerateModel("user", gen.FieldModify(func(f gen.Field) gen.Field {
if f.ColumnName == "id" {
f.GORMTag.Remove(field.TagKeyGormDefault)
}
return f
}))
*/
// 2. 新增一个数据库不存在,但是model上需要的字段
FieldNew
// 3. 根据名称忽略字段
FieldIgnore
//gen.FieldIgnore("create_time") //忽略创建时间字段
// 4. 根据正则表达式忽略字段
FieldIgnoreReg
// 5. 自定义字段的model属性名
FieldRename
//gen.FieldRename("_id","ID") //指定id字段的在model上的名称
// 6. 自定义字段的注释信息,默认会同步数据库的备注信息
FieldComment
// 7. 字段名称指定字段的类型
FieldType
//gen.FieldType("created_time", "int64") // 创建时间戳字段生成int64
//gen.FieldType("status", "BindStatus") // 状态生成自定义类型字段,需要实现Scan/Value
// 8. 根据正则指定字段的类型
FieldTypeReg
// 9. 指定生成dao的类型
FieldGenType
//比如自定义枚举字段默认都会生成一个通用的Field类型,你可以指定成Int64
//gen.FieldGenType("status", "Int64")
// 10. 根据正则指定dao属性类型
FieldGenTypeReg
// 11. 自定义标签
FieldTag
//比如一个加密字段,需要加上kms的标签
/*
gen.FieldTag("phone", func(tag field.Tag) field.Tag {
//tag.Set("kms","xxx")
return tag.Set("kms","xxx")
})
*/
// 12. 自定义json标签
FieldJSONTag
//比如我的字段是int类型,数据源是string类型
// gen.FieldJSONTag("id","id,string,omitempty")
// 13. 自定义json标签
FieldJSONTagWithNS
//比如含有时间的字段,都加上omitempty
/*
gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
if strings.Contains(columnName, "time") {
return columnName + ",omitempty"
}
return columnName
})
*/
// 14. 自定义GROM标签
FieldGORMTag
//比如给某些时间/自定义类型字段加序列化标签
/*
gen.FieldGORMTag("extra", func(tag field.GormTag) field.GormTag {
return tag.Set("serializer", "json")
})
*/
// 15. 根据正则自定义GORM标签
FieldGORMTagReg
// 16. 新增tag
FieldNewTag
//gen.FieldNewTag("phone", field.Tag{"kms": "xxx"})
// 17. 新增tag
FieldNewTagWithNS
//比如统一加上thrif/form标签
/*
gen.FieldNewTagWithNS("form", func(columnName string) (tagContent string) {
return columnName
})
*/
// 18. 删除属性前缀
FieldTrimPrefix
// 19. 删除属性后缀
FieldTrimSuffix
// 20. 增加属性前缀
FieldAddPrefix
// 21. 增加属性后缀
FieldAddSuffix
// 22. 定义关联关系
FieldRelate
//比如在用户model上生成关联的角色属性
/*
role := g.GenerateModel("role")
g.GenerateModel("user", gen.FieldRelate(field.HasMany, "Roles", role,
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"RoleRefer"}},
}),
)
*/
// 23. 用已有的model定义关联关系
FieldRelateModel
//和上面的区别是role不是同步数据库表生成的model,而是历史代码中已有的model
// 24. 给生成的model添加一些通用的方法
WithMethod
//比如加上判断空或者获取名称的方法
/*
type CommonMethod struct {
ID int32
Name *string
}
func (m *CommonMethod) IsEmpty() bool {
if m == nil {
return true
}
return m.ID == 0
}
func (m *CommonMethod) GetName() string {
if m == nil || m.Name == nil {
return ""
}
return *m.Name
}
// 给生成的People model增加 IsEmpty方法
g.GenerateModel("people", gen.WithMethod(CommonMethod{}.IsEmpty))
// 给生成的User model增加 CommonMethod的所有方法
g.GenerateModel("user", gen.WithMethod(CommonMethod))
*/
)
1.3 完整示例
package main
import (
"context"
"strings"
"github.com/go-gorm/gendemo/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
)
// GEN Guideline: https://gorm.io/gen/index.html
// generate code
func main() {
//init db
mysql.Init()
db := mysql.DB(context.Background())
// specify the output directory (default: "./query")
// ### if you want to query without context constrain, set mode gen.WithoutContext ###
g := gen.NewGenerator(gen.Config{
OutPath: "../../biz/dal/query",
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
/* Mode: gen.WithoutContext,*/
//if you want the nullable field generation property to be pointer type, set FieldNullable true
/* FieldNullable: true,*/
})
g.WithOpts(gen.FieldType("deleted_at", "gorm.DeletedAt"))
g.WithTableNameStrategy(func(tableName string) (targetTableName string) {
if strings.HasPrefix(tableName, "_") { //忽略下划线开头的表
return ""
}
return tableName
})
g.WithDataTypeMap(map[string]func(columnType gorm.ColumnType) (dataType string){
"tinyint": func(columnType gorm.ColumnType) (dataType string) {
ct, _ := columnType.ColumnType()
if strings.HasPrefix(ct, "tinyint(1)") {
return "int8"
}
return "int16"
},
})
// reuse the database connection in Project or create a connection here
// if you want to use GenerateModel/GenerateModelAs, UseDB is necessray or it will panic
g.UseDB(db)
// apply basic crud api on structs or table models which is specified by table name with function
// GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute.
// g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))
// apply diy interfaces on structs or table models
// g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))
g.ApplyBasic(
g.GenerateModel("user", gen.FieldModify(func(f gen.Field) gen.Field {
if f.ColumnName == "id" {
f.GORMTag.Remove(field.TagKeyGormDefault)
}
return f
}),
gen.FieldTag("phone", func(tag field.Tag) field.Tag {
//tag.Set("kms","xxx")
return tag.Set("encrypt", "xxx")
}),
gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
if strings.Contains(columnName, "time") {
return columnName + ",omitempty"
}
return columnName
}),
gen.FieldJSONTag("id", "id,string,omitempty"),
gen.FieldIgnore("create_time"),
gen.FieldNewTag("phone", field.Tag{"encrypt": "xxx"}),
gen.FieldNewTagWithNS("form", func(columnName string) (tagContent string) {
return columnName
}),
),
g.GenerateModel("role", gen.FieldType("created_time", "int64"),
gen.FieldGORMTag("extra", func(tag field.GormTag) field.GormTag {
return tag.Set("serializer", "json")
}),
gen.WithMethod(gen.DefaultMethodTableWithNamer),
),
)
//role := g.GenerateModel("role")
//g.GenerateModel("user", gen.FieldRelate(field.HasMany, "Roles", role,
// &field.RelateConfig{
// // RelateSlice: true,
// GORMTag: field.GormTag{"foreignKey": []string{"RoleRefer"}},
// }),
//)
// execute the action of code generation
g.Execute()
}
建表SQL生成
2.1 介绍/使用
背景:一般我们用数据库直接生成就可以了,也没有额外的维护成本。但是由于公司内基本上boe都是用的一个数据库,有些业务会存在同时多个需求都新增字段;需求A上线的时候,会将需求B增加的字读放到model里面带上线,导致线上出现字段不存在的错误。针对这个问题,对于一般的业务其实PPE验证的时候应该就能发现了,然后顺带提下rds工单也能搞定;有些特殊的业务没有PPE验证的逻辑,会导致上线才发现问题,因此有了SQL生成的方式。业务在代码中维护需求设计的建表SQL,然后根据SQL文件生成model。
使用:根据建表SQL生成和同步数据库生成的方式是一致的,唯一不同的是替换db为一个db即可。支持多种方式:
-
建表SQL字符串
-
建表SQL文件
-
建表SQL文件夹
db, _ := gorm.Open(rawsql.New(rawsql.Config{
//SQL: rawsql, //指定建表sql字符串也行
FilePath: []string{
//"./sql/user.sql", // 可以是sql文件,文件里可以是单个建表语句,也可以是多个
"../../conf/sql/", // 指定一个sql文件路径也可以
},
}))
2.2 完整示例
package main
import (
"strings"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
"gorm.io/rawsql"
)
// GEN Guideline: https://gorm.io/gen/index.html
// generate code
func main() {
//init db
//mysql.Init()
//db := mysql.DB(context.Background())
db, _ := gorm.Open(rawsql.New(rawsql.Config{
//SQL: rawsql, //指定建表sql字符串也行
FilePath: []string{
//"./sql/user.sql", // 可以是sql文件,文件里可以是单个建表语句,也可以是多个
"../sql/", // 指定一个sql文件路径也可以
},
}))
// specify the output directory (default: "./query")
// ### if you want to query without context constrain, set mode gen.WithoutContext ###
g := gen.NewGenerator(gen.Config{
OutPath: "../../biz/dal/query",
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
/* Mode: gen.WithoutContext,*/
//if you want the nullable field generation property to be pointer type, set FieldNullable true
/* FieldNullable: true,*/
})
g.WithOpts(gen.FieldType("deleted_at", "gorm.DeletedAt"))
g.WithTableNameStrategy(func(tableName string) (targetTableName string) {
if strings.HasPrefix(tableName, "_") { //忽略下划线开头的表
return ""
}
return tableName
})
g.WithDataTypeMap(map[string]func(columnType gorm.ColumnType) (dataType string){
"tinyint": func(columnType gorm.ColumnType) (dataType string) {
ct, _ := columnType.ColumnType()
if strings.HasPrefix(ct, "tinyint(1)") {
return "int8"
}
return "int16"
},
})
// reuse the database connection in Project or create a connection here
// if you want to use GenerateModel/GenerateModelAs, UseDB is necessray or it will panic
g.UseDB(db)
// apply basic crud api on structs or table models which is specified by table name with function
// GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute.
// g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))
// apply diy interfaces on structs or table models
// g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))
g.ApplyBasic(
g.GenerateModel("user", gen.FieldModify(func(f gen.Field) gen.Field {
if f.ColumnName == "id" {
f.GORMTag.Remove(field.TagKeyGormDefault)
}
return f
}),
gen.FieldTag("phone", func(tag field.Tag) field.Tag {
//tag.Set("kms","xxx")
return tag.Set("encrypt", "xxx")
}),
gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
if strings.Contains(columnName, "time") {
return columnName + ",omitempty"
}
return columnName
}),
gen.FieldJSONTag("id", "id,string,omitempty"),
gen.FieldIgnore("create_time"),
gen.FieldNewTag("phone", field.Tag{"encrypt": "xxx"}),
gen.FieldNewTagWithNS("form", func(columnName string) (tagContent string) {
return columnName
}),
),
g.GenerateModel("role", gen.FieldType("created_time", "int64"),
gen.FieldGORMTag("extra", func(tag field.GormTag) field.GormTag {
return tag.Set("serializer", "json")
}),
gen.WithMethod(gen.DefaultMethodTableWithNamer),
),
)
//role := g.GenerateModel("role")
//g.GenerateModel("user", gen.FieldRelate(field.HasMany, "Roles", role,
// &field.RelateConfig{
// // RelateSlice: true,
// GORMTag: field.GormTag{"foreignKey": []string{"RoleRefer"}},
// }),
//)
// execute the action of code generation
g.Execute()
}
已有的model生成
3.1 介绍/使用
背景:这种场景主要针对,历史项目已经写好了model,想要直接复用;GEN提供这种支持,不需要通过表或者SQL生成model,仅仅是通过model生成对应的dao逻辑。
使用:使用还是参考第一部分,只是前面提到的选项和配置基本都不可用了,当然对于自定义的model已经没有意义了,所有的类型标签等体现在model上。
g.ApplyBasic(
model.User{},
model.Role{},
)
3.2 完整示例
package main
import (
"github.com/go-gorm/gendemo/biz/dal/model"
"gorm.io/gen"
"gorm.io/gorm"
"gorm.io/rawsql"
)
// GEN Guideline: https://gorm.io/gen/index.html
// generate code
func main() {
//init db
// mysql.Init()
//db := mysql.DB(context.Background())
// specify the output directory (default: "./query")
// ### if you want to query without context constrain, set mode gen.WithoutContext ###
g := gen.NewGenerator(gen.Config{
OutPath: "../../biz/dal/query",
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
/* Mode: gen.WithoutContext,*/
//if you want the nullable field generation property to be pointer type, set FieldNullable true
/* FieldNullable: true,*/
})
g.ApplyBasic(
model.User{},
model.Role{},
)
// execute the action of code generation
g.Execute()
}
生成Tips
4.1 为生成的model生成额外的方法
你是否也想生成的model可以带上一些通用的简单方法,比如判断是否为空等
package diy
import "gorm.io/gen"
type Querier interface {
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (gen.T, error) // GetByID query data by id and return it as *struct*
// SELECT * FROM @@table WHERE id IN @ids
MGet(ids ...string) ([]*gen.T, error)
// QueryWith
//SELECT * FROM @@table
// {{if p != nil}}
// {{if p.ID > 0}}
// WHERE id=@p.ID
// {{else if p.Name != ""}}
// WHERE name=@p.Name
// {{end}}
// {{end}}
QueryWith(p *gen.T) (gen.T, error)
}
4.2 动态SQL,自定义dao方法
你是否有时候感觉,基础的方法还是不够用,希望能自定义一些dao方法?
完整 demo:github.com/go-gorm/gen…
更多模版参考:gorm.io/gen/dynamic…
package main
import (
"context"
"strings"
"github.com/go-gorm/gendemo/biz/dal/diy"
"github.com/go-gorm/gendemo/biz/dal/model"
"github.com/go-gorm/gendemo/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
)
// GEN Guideline: https://gorm.io/gen/index.html
// generate code
func main() {
//init db
mysql.Init()
db := mysql.DB(context.Background())
// specify the output directory (default: "./query")
// ### if you want to query without context constrain, set mode gen.WithoutContext ###
g := gen.NewGenerator(gen.Config{
OutPath: "../../biz/dal/query",
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
/* Mode: gen.WithoutContext,*/
//if you want the nullable field generation property to be pointer type, set FieldNullable true
/* FieldNullable: true,*/
})
g.WithOpts(gen.FieldType("deleted_at", "gorm.DeletedAt"))
g.WithTableNameStrategy(func(tableName string) (targetTableName string) {
if strings.HasPrefix(tableName, "_") { //忽略下划线开头的表
return ""
}
return tableName
})
g.WithDataTypeMap(map[string]func(columnType gorm.ColumnType) (dataType string){
"tinyint": func(columnType gorm.ColumnType) (dataType string) {
ct, _ := columnType.ColumnType()
if strings.HasPrefix(ct, "tinyint(1)") {
return "int8"
}
return "int16"
},
})
// reuse the database connection in Project or create a connection here
// if you want to use GenerateModel/GenerateModelAs, UseDB is necessray or it will panic
g.UseDB(db)
// apply basic crud api on structs or table models which is specified by table name with function
// GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute.
// g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))
// apply diy interfaces on structs or table models
// g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))
g.ApplyInterface(func(diy.Querier) {},
g.GenerateModel("user", gen.FieldModify(func(f gen.Field) gen.Field {
if f.ColumnName == "id" {
f.GORMTag.Remove(field.TagKeyGormDefault)
}
return f
}),
gen.FieldTag("phone", func(tag field.Tag) field.Tag {
//tag.Set("kms","xxx")
return tag.Set("encrypt", "xxx")
}),
gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
if strings.Contains(columnName, "time") {
return columnName + ",omitempty"
}
return columnName
}),
gen.FieldJSONTag("id", "id,string,omitempty"),
gen.FieldIgnore("create_time"),
gen.FieldNewTag("phone", field.Tag{"encrypt": "xxx"}),
gen.FieldNewTagWithNS("form", func(columnName string) (tagContent string) {
return columnName
}),
),
model.Role{},
)
//role := g.GenerateModel("role")
//g.GenerateModel("user", gen.FieldRelate(field.HasMany, "Roles", role,
// &field.RelateConfig{
// // RelateSlice: true,
// GORMTag: field.GormTag{"foreignKey": []string{"RoleRefer"}},
// }),
//)
// execute the action of code generation
g.Execute()
}
package diy
import "gorm.io/gen"
type Querier interface {
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (gen.T, error) // GetByID query data by id and return it as *struct*
// SELECT * FROM @@table WHERE id IN @ids
MGet(ids ...string) ([]*gen.T, error)
// QueryWith
//SELECT * FROM @@table
// {{if p != nil}}
// {{if p.ID > 0}}
// WHERE id=@p.ID
// {{else if p.ShopID != ""}}
// WHERE created_time=@p.ShopID
// {{end}}
// {{end}}
QueryWith(p *gen.T) (gen.T, error)
}
下一篇:GORM GEN的CRUD