Gorm/Gen 代码生成 | 青训营

128 阅读3分钟

为什么用Gen生成代码

字段类型校验,过滤参数错误,为数字、字符串、布尔类型、时间类型硬编码制定差异化类型安全的表达式方法,杜绝了 SQL 注入的风险,能跑就安全;

映射数据库表像,DB 里面有数据表就能生成对应的 Golang 结构体;

用注释的形式描述查询的逻辑后,一键即可生成对应的安全可靠查询API。

作者:ag9920 链接:juejin.cn/post/713315… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文用到的工具大概关系如下图

步骤

安装

go get -u gorm.io/gen

创建数据库模型

新建一个go文件在model下,例如

model/publish.go就是数据库中publish表的数据,我们后面要对它进行PO

// Publish 视频数据
type Publish struct {
    gorm.Model
    UserId   int64  `json:"user_id" gorm:"not null"`
    Title    string `json:"title" gorm:"varchar(20);not null"`
    PlayUrl  string `json:"play_url" gorm:"varchar(255);not null"`
    CoverUrl string `json:"cover_url" gorm:"varchar(255);not null"`
}

Gorm 会自动生成Row_ID, created_at, deleted_at, changed_at

创建数据库表

根据上一步的模型使用 gorm 来创建 table, 依赖的是 Gorm 的 Migrate能力。这里添加你刚刚弄的struct:

    err = db.AutoMigrate(&model.Publish{})
    if err != nil {
       panic("publish model has changed == > transfer to new model struct" + err.Error())
    }

最后run一下这个go

// use gorm to migrate database
func main() {
    db, err := gorm.Open(mysql.Open(config.AppConfig.DSN), &gorm.Config{
       DisableForeignKeyConstraintWhenMigrating: true,
    })

    if err != nil {
       panic("mysql - database connect error  == > " + err.Error())
    }

    // migrate user model
    err = db.AutoMigrate(&model.User{})
    if err != nil {
       panic("user model has changed == > transfer to new model struct" + err.Error())
    }

    // migrate publish model
    err = db.AutoMigrate(&model.Publish{})
    if err != nil {
       panic("publish model has changed == > transfer to new model struct" + err.Error())
    }

}

run这个文件,检查数据库是否新增了一个表

生成代码文件

运行cmd/gorm-gen目录下的gen.go,这里是调用gorm/gen的动态生成能力实现数据库操作的代码生成

其中dynamic SQL的约定可以参考官网gorm.io/gen/dynamic…

type Querier interface {
    // GetByRoles query data by roles and return it as *slice of pointer*
    //   (The below blank line is required to comment for the generated method)
    // //
    // SELECT * FROM @@table WHERE role IN @rolesName
    GetByRoles(rolesName ...string) ([]*gen.T, error)
}

func generateModelByGen() {
    gormgen := gen.NewGenerator(gen.Config{
       OutPath: "repo",
       Mode:    gen.WithDefaultQuery | gen.WithQueryInterface | gen.WithoutContext,
    })

    gormDB, err := gorm.Open(mysql.Open(config.AppConfig.DSN))
    fmt.Println("gormDB", config.AppConfig.DSN)
    if err != nil {
       panic("mysql - database connect error  == > " + err.Error())
    }
    gormgen.UseDB(gormDB)

    // generate basic DAO API for struct 'model.Publish'
    gormgen.ApplyBasic(model.Publish{})

    // generate type-safe API for struct 'model.Publish'
    gormgen.ApplyInterface(func(Querier) {}, model.Publish{})

    gormgen.Execute()
}

func main() {
    generateModelByGen()
}

其中,Querier定义了一个动态SQL,代表的SQL语句也按照gorm/gen的约定描述在注释中,所以gen会按照这个生成对应的go操作函数。

指定我们要生成的代码要放到 repo 子包,生成模式暂时用 default 就ok。

我们通过 gen.NewGenerator 来构造一个【代码生成器】,

调用 ApplyBasic 基于两个 model 来生成基础 DAL 代码;

调用 ApplyInterface,指明我们希望基于什么 model 和 interface 来生成自定义的接口实现。

最后调用 Execute 方法来触发生成。

作者:ag9920 链接:juejin.cn/post/713315… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

运行main函数后,repo文件夹下应该有生成的函数。

使用

至此,我们就实现了使用gorm/gen实现数据库操作的代码生成。

然后就可以开始使用生成的代码对数据库进行CRUD啦!!

// q is the query struct gorm use to connect db
q := repo.Q

// connect to your model
publishIDo := q.Publish
// do it with/without context
publishIDo = q.WithContext(ctx).Publish

// then do gorm CRUD
newPublishModel := model.Publish{
    UserId:   123456,
    Title:    req.Title,
    PlayUrl:  fileLink,
    CoverUrl: coverLink,
}
fmt.Println("new publish model == > ", newPublishModel)

//repo.Q.New
err = publishIDo.Create(&newPublishModel)

在测试中

这里有一个小坑。在测试中gorm好像不会想在全局运行时一样初始化拿到数据库连接,需要自己设置一下:

func TestDouyinPublishActionServiceImpl_DouyinPublishAction(t *testing.T) {
    // get video file in curent directory
    testFile, err := os.ReadFile("./resource/test.mp4")
    if err != nil {
       t.Fatal(err)
    }
   repo.SetDefault(repo.DB) 

Credit

gorm.io/gen/

juejin.cn/post/713315…