最近准备上手一个新项目,然后在选择底层的 ORM 框架
我之前一直用的 GORM ,其实感觉还挺好用的,但是毕竟用多了,这次也想了解下其他的解决方案
之前在和群友讨论 GORM 的时候,我听见有人对它有一些负面的看法,比如说认为 GORM 不算真正的 ORM,因为它很多时候只是在帮你手动拼凑 SQL 语句,只是没有写原生 SQL 那么痛苦而已,我听了之后感觉其实也有点道理
比如说我最近项目里的一句查询
1 2 3 4 5 l.svcCtx.DBList.Mysql. Where("from_id = ? and to_user_id = ?" , in.UserAId, in.UserBId). Or("from_id = ? and to_user_id = ?" , in.UserBId, in.UserAId). Order("created_at desc" ). First(&result)
其中还是要手打 from_id
和 to_user_id
这些字段名,就像原生 SQL 一样,而真正的 ORM 不应当是这样的,应当走如同 ent 这种代码生成的路子
甚至我也听过有人认为不应该使用 ORM 框架:为什么要旗帜鲜明地反对 orm 和 sql builder
也就是说你的 ORM 虽然方便,但是不方便进行 SQL 语句的审查,也就是说你不能预测线上环境会生成哪些 SQL,会有不确定性
但是其实我觉得吧,他说的场景我现在都还遇不到,我的项目也就是一些简单的 CURD ,而且数据量也不大
那么现在的情况就是说,我可以去尝试一下代码生成类型的 ORM ,例如 ent,也可以尝试其他方案,如 sqlx、sqlc 之类的
但是我真的离不开 GORM 的关联关系哇,真的是太方便了(我知道 ent 也有类似的设计,但我还是习惯 GORM 的)
然后我想起来 GORM 其实也推出了一个代码生成的版本,也就是 GEN 模式:https://gorm.io/zh_CN/gen/ ,之前在B站也刷到了 BV1Es4y1W7eg
我就打算尝试一下这个 GORM 的 GEN 模式(下面简称 GEN 了)
GEN 支持从数据库进行代码生成,也可以通过已经存在的 GORM model 生成代码
下面就尝试从 model 生成,就拿经典的老师学生一对多关系举例
首先把包拉一下
在项目根目录新建 model
文件夹,然后创建 model.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package modeltype Student struct { Id int Name string TeacherID int } type Teacher struct { Id int Name string Student []Student }
然后我们准备生成代码了,但是不是用一个命令生成,是用一个 golang 程序,里面包含了配置,我感觉这样的设计有点意思
退回项目根目录,新建 build
文件夹,然后创建 build.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "gorm-gen/model" "gorm.io/gen" ) func main () { g := gen.NewGenerator(gen.Config{ OutPath: "./query" , Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, }) g.ApplyBasic(model.Student{}, model.Teacher{}) g.Execute() }
GEN 还支持动态 SQL ,这东西我感觉我还用不上,就没折腾
执行之后,应该能生成 query
目录
1 2 3 4 5 6 7 8 9 10 11 . ├── build │ └── main.go ├── go.mod ├── go.sum ├── model │ └── model.go └── query ├── gen.go ├── students.gen.go └── teachers.gen.go
然后你就可以开始写 main.go
了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package mainimport ( "fmt" "gorm-gen/model" "gorm-gen/query" "gorm.io/driver/mysql" "gorm.io/gorm" ) func main () { dsn := "root:12345678@tcp(127.0.0.1:3306)/gorm_learning?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ DisableForeignKeyConstraintWhenMigrating: true , }) if err != nil { panic (err) } err = db.AutoMigrate(&model.Student{}, &model.Teacher{}) if err != nil { panic (err) } query.SetDefault(db) student1 := model.Student{Name: "student1" } student2 := model.Student{Name: "student2" } student3 := model.Student{Name: "student3" } _ = query.Student.Create(&student1, &student2, &student3) teacher1 := model.Teacher{Name: "teacher1" } _ = query.Teacher.Create(&teacher1) _, _ = query.Student.Where(query.Student.Id.Eq(3 )).Delete() _, _ = query.Student.Where(query.Student.Id.Eq(2 )).Update(query.Student.Name, "student2_new" ) student, _ := query.Student.Where(query.Student.Id.Eq(1 )).Take() teacher, _ := query.Teacher.Where(query.Teacher.Id.Eq(1 )).Take() fmt.Println(student) fmt.Println(teacher) _ = query.Teacher.Student.Model(&teacher1).Append(&student1, &student2) teacher, _ = query.Teacher.Preload(query.Teacher.Student).Where(query.Teacher.Id.Eq(1 )).Take() fmt.Println(teacher) }
观察执行语句,你会发现与原版 GORM 的不同,比如说你在更新字段的时候,原版应该是这样写的
1 GLOBAL_DB.Model(&Student{}).Where("ID = ?" , 2 ).Update("Name" , "student2_new" )
而现在变成了这样
1 query.Student.Where(query.Student.Id.Eq(2 )).Update(query.Student.Name, "student2_new" )
我感觉这样更能避免错误,看上去也更加安全,我这次项目就打算尝试一下用这个 GEN 模式了