Gorm 和 Gen 介绍
Gorm
关于Gorm的特点,其网上介绍的有以下方面:
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持
Preload、Joins的预加载 - 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
GEN
我们在使用Gorm时,一步到位使用了Gen。
GEN 是一个基于 GORM 的安全 ORM 框架, 由字节跳动无恒实验室与 GORM 作者联合研发,主要功能说白了就是帮助生成数据表对应的模型文件和更安全方便地执行SQL。
使用例子: 创建一个表, 用户之间的关注关系
直接使用Gorm和使用Gen的不同
直接使用GORM 使用GEN
- 对于结构体的生成,Gorm需手动创建与数据表各列一一对应的结构体,而Gen指定表名后就能自动读取并生成对应结构体;
- 在查询逻辑上,Gorm需要手动实现具体的 go 代码查询逻辑,而Gen只需描述SQL的查询逻辑即可,其工具自动转换成安全稳定的代码;
- 在查询语句生成方面,虽然Gorm的查询接口十分灵活,但不能保持查询的 SQL 不发生语法错误,而 只能通过测试保证部分场景的正常运行;而Gen只要查询接口使用类型安全,编译可通过,那么它的查询逻辑即是正常合理的;
- 在安全方面,Gorm需人工评经验保证业务不存在安全问题,一旦出错往往在上线前才能发现,影响上线流程,而Gen提供的安全可靠的查询 API,开发时能用的就是安全的
对我和小组而言,Gorm最方便的还是其ORM特性。 由于我们组的Gorm是其他同学配置并建设的,所以我在此记录一下网上公开的配置部分,留待以后参考。
配置 GEN 并生成模型结构体
使用gorm库操作MYSQL步骤是:
- 使用struct定义模型,模型主要用在golang中代表mysql表
- 使用gorm创建数据库连接
- 使用gorm操作数据库。
我们在项目根目录下定义一个ddl.sql文件,这是定义模型的代码:(部分)
# drop database douyin;
create database douyin;
use douyin;
create table follower
(
id int auto_increment comment '主键'
primary key,
follower_id int not null comment '粉丝用户ID',
following_id int not null comment '被关注用户ID',
create_time timestamp default CURRENT_TIMESTAMP not null comment '关注时间'
)
创建并初始化项目,再引入 GEN 包
mkdir douyin && cd douyin
go mod init
go get -u gorm.io/gen@v0.3.16
之后,创建用于生成模型文件的 ./cmd/generate/main.go文件,用于执行 SQL 示例的 ./main.go文件已在项目中。
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gorm"
)
// Dynamic SQL
type Querier interface {
// SELECT * FROM @@table WHERE name = @name{{if role !=""}} AND role = @role{{end}}
FilterWithNameAndRole(name, role string) ([]gen.T, error)
}
func main() {
g := gen.NewGenerator(gen.Config{
OutPath: "../../internal/model/query",
ModelPkgPath: "db_model",
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
})
// 改成实际的
// 一些数据库设置,设计信息略过
g.UseDB(db) // reuse your gorm db
g.ApplyBasic(g.GenerateModel("follower"))
g.Execute()
}
执行生成程序,gen能够对设计的模型文件转化出相关文件:
go mod tidy
go run ./cmd/generate/main.go
如图所示:
实际使用记录
对于我负责的业务,我要做的一个部分是实现用户对另一个用户的关注。在我们的项目中,controller部分获取前端发来的query参数,经过简单提取处理之后,交给service部分进行认证和业务逻辑处理,而对于数据库的操作就由service调用operator来完成,operator中有操作数据库的语句,这些语句的功能是靠着gen.go来完成的,省去了直接写sql的不确定性。
例如:
func CreateUserFollowRelation(userId, toUserId int32) error {
l:= query.Follower
err := l.Create(&db_model.Follower{
FollowerID: userId,
FollowingID: toUserId,
// ……
})
这里query.Follower就是Gen生成的Gorm部分,
type Follower struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
FollowerID int32 `gorm:"column:follower_id;not null;comment:ID" json:"follower_id"` // ID
FollowingID int32 `gorm:"column:following_id;not null;comment:ID" json:"following_id"` // ID
CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP" json:"create_time"`
}
虽然使用到了gen生成的,但是还是要记录一下,ID作为主键是自增长的。 还要记录几个对应关系:
模型名和表名的对应关系:
- 第一个大写字母变为小写;
- 遇到其他大写字母变为小写并且在前面加下划线;
- 连着的几个大写字母,只有第一个遵循上面的两条规则,其他的大写字母转为小写,不加下划线,遇到小写,前面的第一个大写字母变小写并加下划线; *复数形式;
也就是对应着上面,Follower => followers
结构体字段名和列名的对应规则
列名是字段名的蛇形小写