一、引言
简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。
在Go中,ORM可以把数据库里的表格映射成 Go 语言中的结构体,然后你只需要像操作对象那样去增删改查数据,而不必烦恼那些冗长的 SQL 语句,同时很多 ORM 框架都支持多种数据库,切换底层数据库时只需要做简单配置
但受限于Go本身的语法,目前主流的ORM,大部分都非常的难用
萝卜青菜各有所爱,本系列为大家介绍一些主流的Go ORM,供大家对比选择
二、常见ORM
在v站的一篇帖子下,我收集了回帖中提到比较多的ORM,并根据内部实现机制和设计理念,将Go ORM 框架分成了三类
🌲 反射型 主要通过反射机制将结构体映射到数据库表上,代表作为 go-gorm/gorm
🌲 代码生成型 通过代码生成工具预先生成数据模型及查询构建器,代表作有 ent/ent 和日益流行的 go-gorm/gen
🌲 SQL 增强型 基于原生 SQL 库进行封装和扩展,既保留 SQL 的灵活性,又提供了一系列便捷函数,代表作为 jmoiron/sqlx
实现方式 | Go ORM库 | star |
---|---|---|
reflect to struct | go-gorm/gorm | 36.6k |
reflect to struct | go-xorm/xorm | 6.7k |
reflect to struct | upper/db | 3.5k |
Code gen | go-gorm/gen | 2.3k |
Code gen | ent/ent | 15.9k |
Code gen | sqlc-dev/sqlc | 14.2k |
SQL enhance | Masterminds/squirrel | 7.1k |
SQL enhance | jmoiron/sqlx | 16.6k |
1、反射型
反射型 ORM 利用 Go 语言的反射机制,在运行时将结构体的字段和标签动态映射到数据库表中,自动生成 SQL 语句来实现 CRUD 操作。这种方式无需预先生成代码,使用较为简单直观
优势:
- 开发简便:直接基于结构体定义进行数据库操作,无需额外生成代码
- 动态灵活:自动识别结构体字段和标签,支持自动迁移和关联查询
缺点:
- 性能开销:反射机制会引入运行时开销,在高并发场景下可能成为瓶颈
- 类型安全不足:导致部分类型错误依赖运行时检查,无法在编译期捕获
go-gorm/gorm 是目前最流行的反射型 ORM 框架,甚至可以说是Go中最流行的ORM 框架
个人感受,受限Go语法加上作者的设计思路,导致gorm过度依赖 interface{}
,各种隐式规则,使用时相当不方便,有一种让Go退化成了弱类型的感觉,不仅没有办法做到字段提示,每次传参都得看文档
其示例代码如下:
go
package main
import (
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string `gorm:"not null"`
Email string `gorm:"unique;not null"`
}
func main() {
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
// 创建记录
db.Create(&User{Name: "Alice", Email: "alice@example.com"})
// 查询记录
var user User
db.First(&user, "name = ?", "Alice")
log.Printf("User: %+v\n", user)
}
2、代码生成型
因为Go语法限制,反射实现注定没有办法做到类型安全,因此催生出了代码生成这个流派
代码生成型 ORM 框架通过解析开发者定义的 schema,然后生成类型安全的代码文件,这种方式能在编译时捕获类型错误,同时避免运行时反射带来的性能损耗
虽然使用前需要执行代码生成步骤,但在大型项目中,这种方式能提供更高的安全性和性能
优势:
- 编译期类型检查:生成的代码可以在编译时捕获错误
- 高运行时性能:无需依赖反射,运行时效率更高
缺点:
- 额外生成步骤:需要预先运行代码生成命令,增加构建流程
- 开发流程复杂:生成代码与手写代码之间的同步和维护可能增加开发难度,尤其在频繁修改 schema 时
go-gorm/gen 是与 GORM 配套的代码生成工具,是代码生成型ORM的后起之秀,为 GORM 提供了更强的类型安全查询接口,提升了性能和编译时检查能力
ent/ent 是目前代码生成型ORM中最流行的,个人相对比较喜欢,虽然跟Rust或者TS的ORM相比体验还是差很多, 哎,没办法啊
Ent需要开发者先定义 schema,然后通过命令行工具生成代码。生成的代码包含了模型、查询构建器和数据库迁移工具,从而确保数据库操作的类型安全和高效性
以下示例忽略了schema和代码生成部分
go
package main
import (
"context"
"log"
"entgo.io/ent/dialect/sql"
_ "github.com/mattn/go-sqlite3"
"entgo.io/ent/examples/start/ent"
)
func main() {
client, err := ent.Open(sqlite.Open("./ent.db"), &sql.Config{Driver: "sqlite3"})
if err != nil {
log.Fatal("failed to connect to the database:", err)
}
defer client.Close()
// 创建一个新用户
user, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(context.Background())
if err != nil {
log.Fatal(err)
}
log.Println("User created:", user)
}
3、SQL增强型库
有一部分人认为ORM是一个鸡肋的概念,尤其在Go中,因此选择裸写SQL的方式
SQL增强型库在原生 SQL 库的基础上进行扩展,不隐藏 SQL 语句,而是通过提供额外的封装函数和数据映射机制,帮助开发者更高效地执行查询和数据转换。它保持了 SQL 的灵活性,同时简化了常见操作。
优势:
- 灵活性高:保留 SQL 语句的编写自由,适合复杂查询
- 数据映射简化:自动绑定查询结果到结构体,减少手动转换代码
- 轻量高效:直接使用原生 SQL,无额外 ORM 开销
缺点:
- 需要手写 SQL:开发者必须自行编写 SQL,可能会增加样板代码和维护难度
- 缺少高级抽象:没有 ORM 提供的自动关联、迁移和数据验证功能
jmoiron/sqlx 是一款流行的 SQL 增强型库,其示例代码如下:
go
package main
import (
"log"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
func main() {
db, err := sqlx.Connect("sqlite3", "sqlx.db")
if err != nil {
log.Fatal(err)
}
// 查询数据
var user User
err = db.Get(&user, "SELECT * FROM users WHERE id=?", id)
if err != nil {
log.Fatal(err)
}
log.Printf("User: %+v\n", user)
}
三、总结
总结下:Go ORM 框架各有优势与不足
- 反射型(如 GORM)开发简单、上手快,但反射带来的性能开销和语法缺陷,导致用起来很别扭,但好在GORM应用广泛,网上的资料和文档也比较多
- 代码生成型(如 Ent、go-gorm/gen)通过生成类型安全的代码提供了更高的性能和编译时检查,但需要额外的代码生成步骤,开发流程相对复杂
- SQL增强型库(如 sqlx)在保留 SQL 灵活性的同时,通过扩展封装提升了开发效率,非常适合不喜欢ORM概念的开发者
总之,受限Go语法目前没有特别好用的ORM库,大家只能根据个人喜欢和流行程度来自行选择了
后续会为大家带来每个ORM库的详细用法的介绍
✨ 微信公众号【凉凉的知识库】同步更新,欢迎关注获取最新最有用的知识 ✨