• Go中主流的ORM框架优劣势对比
  • 发布于 1周前
  • 73 热度
    0 评论
一、引言
简单说,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退化成了弱类型的感觉,不仅没有办法做到字段提示,每次传参都得看文档。
其示例代码如下:
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)
 }
// 堆代码 duidaima.com
// 创建记录
 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和代码生成部分
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 增强型库,其示例代码如下:
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 框架各有优势与不足:
1.反射型(如 GORM)开发简单、上手快,但反射带来的性能开销和语法缺陷,导致用起来很别扭,但好在GORM应用广泛,网上的资料和文档也比较多
2.代码生成型(如 Ent、go-gorm/gen)通过生成类型安全的代码提供了更高的性能和编译时检查,但需要额外的代码生成步骤,开发流程相对复杂

3.SQL增强型库(如 sqlx)在保留 SQL 灵活性的同时,通过扩展封装提升了开发效率,非常适合不喜欢ORM概念的开发者


总之,受限Go语法目前没有特别好用的ORM库,大家只能根据个人喜欢和流行程度来自行选择了。
用户评论