mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1524 字
8 分钟
GO项目ORM框架对比与选择

环境搭建#

通过docker部署一个mysql8.0容器,然后在里面创建三个db,用来快速进行实现gorm、xorm、ent三种社区常用的orm框架对比。

部署Mysql Docker容器#

# 直接运行MySQL 8.0容器,不挂载任何数据卷
docker run -d \
--name mysql-orm-test \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123 \
-e MYSQL_DATABASE=orm_base \
m.daocloud.io/docker.io/library/mysql:8.0 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci \
--max_connections=1000
# 验证运行
docker ps | grep mysql

创建三个隔离的数据库#

# 进入MySQL容器执行命令
docker exec -it mysql-orm-test mysql -uroot -proot123
# 在MySQL中执行以下SQL创建三个数据库
CREATE DATABASE IF NOT EXISTS gorm_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS xorm_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS ent_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 查看创建的数据库
SHOW DATABASES LIKE '%demo%';

测试场景:用户与文章(两张表)#

这里的建表语句简单看看即可,在三种框架对比时,会分别用框架迁移能力实现表的创建。

-- 表1: users
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username)
);
-- 表2: posts (关联users表)
CREATE TABLE posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
title VARCHAR(200) NOT NULL,
content TEXT,
view_count INT DEFAULT 0,
is_published BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at)
);

如果手动创建,则需要分别为三个database去创建这两张表,太繁琐。

三个框架的DEMO实现#

项目结构#

orm-demo/
├── gorm-demo/ # GORM实现
│ ├── go.mod
│ ├── main.go
│ └── internal/
├── xorm-demo/ # XORM实现
│ ├── go.mod
│ └── main.go
├── ent-demo/ # Ent实现
│ ├── go.mod
│ └── ent/
└── benchmark/ # 性能测试
├── main.go
└── runners/

GORM实现(使用gorm_demo数据库)#

package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"time"
)
type User struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"size:50;uniqueIndex"`
Email string `gorm:"size:100;uniqueIndex"`
Posts []Post `gorm:"foreignKey:UserID"`
CreatedAt time.Time
UpdatedAt time.Time
}
type Post struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"index"`
User User `gorm:"foreignKey:UserID"`
Title string `gorm:"size:200"`
Content string `gorm:"type:text"`
ViewCount int `gorm:"default:0"`
IsPublished bool `gorm:"default:true"`
CreatedAt time.Time
}
func main() {
// 连接到gorm_demo数据库
dsn := "root:root123@tcp(localhost:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Fatal("连接数据库失败:", err)
}
// 自动迁移(创建表)
db.AutoMigrate(&User{}, &Post{})
fmt.Println("GORM demo 数据库已初始化")
// 演示CRUD操作
demoCRUDOperations(db)
}
func demoCRUDOperations(db *gorm.DB) {
// 创建用户
user := User{
Username: "john_doe",
Email: "john@example.com",
}
db.Create(&user)
// 创建关联文章
post := Post{
UserID: user.ID,
Title: "GORM使用指南",
Content: "这是一个关于GORM的教程...",
ViewCount: 100,
}
db.Create(&post)
// 查询用户及其文章(预加载)
var fetchedUser User
db.Preload("Posts").First(&fetchedUser, "username = ?", "john_doe")
fmt.Printf("用户: %s, 文章数: %d\n", fetchedUser.Username, len(fetchedUser.Posts))
}

XORM实现(使用xorm_demo数据库)#

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
"time"
"xorm.io/xorm"
"xorm.io/xorm/names"
)
type User struct {
ID int64 `xorm:"'id' pk autoincr"`
Username string `xorm:"'username' varchar(50) unique notnull"`
Email string `xorm:"'email' varchar(100) unique notnull"`
CreatedAt time.Time `xorm:"'created_at' created"`
UpdatedAt time.Time `xorm:"'updated_at' updated"`
}
type Post struct {
ID int64 `xorm:"'id' pk autoincr"`
UserID int64 `xorm:"'user_id' index notnull"`
Title string `xorm:"'title' varchar(200) notnull"`
Content string `xorm:"'content' text"`
ViewCount int `xorm:"'view_count' default 0"`
IsPublished bool `xorm:"'is_published' default 1"`
CreatedAt time.Time `xorm:"'created_at' created"`
}
func (User) TableName() string {
return "users"
}
func (Post) TableName() string {
return "posts"
}
func main() {
// 连接到xorm_demo数据库
dsn := "root:root123@tcp(localhost:3306)/xorm_demo?charset=utf8mb4&parseTime=true"
engine, err := xorm.NewEngine("mysql", dsn)
if err != nil {
log.Fatal("连接数据库失败:", err)
}
// 显示SQL日志
engine.ShowSQL(true)
// 设置表名映射规则
engine.SetMapper(names.GonicMapper{})
// 同步表结构
err = engine.Sync2(new(User), new(Post))
if err != nil {
log.Fatal("同步表结构失败:", err)
}
fmt.Println("XORM demo 数据库已初始化")
// 演示CRUD操作
demoCRUDOperations(engine)
}
func demoCRUDOperations(engine *xorm.Engine) {
session := engine.NewSession()
defer session.Close()
// 开始事务
session.Begin()
// 创建用户
user := &User{
Username: "jane_smith",
Email: "jane@example.com",
}
_, err := session.Insert(user)
if err != nil {
session.Rollback()
log.Fatal(err)
}
// 创建文章
post := &Post{
UserID: user.ID,
Title: "XORM性能分析",
Content: "XORM是一个简单而强大的ORM...",
ViewCount: 150,
}
_, err = session.Insert(post)
if err != nil {
session.Rollback()
log.Fatal(err)
}
// 提交事务
session.Commit()
// 查询用户及其文章
var posts []Post
err = engine.Where("user_id = ?", user.ID).Find(&posts)
if err != nil {
log.Fatal(err)
}
fmt.Printf("用户ID: %d, 文章数: %d\n", user.ID, len(posts))
}

Ent实现(使用ent_demo数据库)#

ent命令行工具安装#

# 安装ent命令行工具
go install entgo.io/ent/cmd/ent@latest
# 查看使用帮助
ent --help

生成schema初始文件#

使用下面的命令生成user和post的初始模板:

ent init User Post

ent/schema/user.go初始内容:

package schema
import "entgo.io/ent"
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}
// Fields of the User.
func (User) Fields() []ent.Field {
return nil
}
// Edges of the User.
func (User) Edges() []ent.Edge {
return nil
}

ent/schema/post.go初始内容:

package schema
import "entgo.io/ent"
// Post holds the schema definition for the Post entity.
type Post struct {
ent.Schema
}
// Fields of the Post.
func (Post) Fields() []ent.Field {
return nil
}
// Edges of the Post.
func (Post) Edges() []ent.Edge {
return nil
}

在执行完new后,自动生成了user和post的实体,但是Fields方法和Edges都是直接返回nil。

需要在user和post里完成完成字段的定义。

定义schema实体#

ent/schema/user.go

package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/edge"
)
type User struct {
ent.Schema
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("username").
Unique().
MaxLen(50),
field.String("email").
Unique().
MaxLen(100),
}
}
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("posts", Post.Type),
}
}

ent/schema/post.go

package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/edge"
)
type Post struct {
ent.Schema
}
func (Post) Fields() []ent.Field {
return []ent.Field{
field.String("title").
MaxLen(200),
field.Text("content"),
field.Int("view_count").
Default(0),
field.Bool("is_published").
Default(true),
}
}
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.From("author", User.Type).
Ref("posts").
Unique().
Required(),
}
}

生成Ent代码#

# 生成代码(需先cd到ent-demo)
go generate ./ent

Ent Demo实现#

ent-demo/main.go:

package main
import (
"context"
"fmt"
"log"
"orm-demo/ent-demo/ent"
_ "github.com/go-sql-driver/mysql"
"orm-demo/ent-demo/ent/user"
)
func main() {
// 连接到ent_demo数据库
dsn := "root:root123@tcp(localhost:3306)/ent_demo?charset=utf8mb4&parseTime=True"
client, err := ent.Open("mysql", dsn)
if err != nil {
log.Fatalf("连接数据库失败: %v", err)
}
defer client.Close()
ctx := context.Background()
// 运行自动迁移
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("创建Schema失败: %v", err)
}
fmt.Println("Ent demo 数据库已初始化")
// 演示CRUD操作
demoCRUDOperations(ctx, client)
}
func demoCRUDOperations(ctx context.Context, client *ent.Client) {
// 创建用户
userData, err := client.User.
Create().
SetUsername("alice_wang").
SetEmail("alice@example.com").
Save(ctx)
if err != nil {
log.Fatalf("创建用户失败: %v", err)
}
// 为用户创建文章
_, err = client.Post.
Create().
SetTitle("Ent框架入门").
SetContent("Ent是一个基于代码生成的ORM框架...").
SetViewCount(200).
SetAuthor(userData).
Save(ctx)
if err != nil {
log.Fatalf("创建文章失败: %v", err)
}
// 查询用户及其文章(预加载)
userWithPosts, err := client.User.
Query().
Where(user.IDEQ(userData.ID)).
WithPosts().
Only(ctx)
if err != nil {
log.Fatalf("查询用户失败: %v", err)
}
// 获取关联的文章
posts, err := userWithPosts.QueryPosts().All(ctx)
if err != nil {
log.Fatalf("查询文章失败: %v", err)
}
fmt.Printf("用户: %s, 文章数: %d\n", userWithPosts.Username, len(posts))
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

GO项目ORM框架对比与选择
https://hua-ri.cn/posts/go项目orm框架对比与选择/
作者
花日
发布于
2025-10-14
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时