网站Logo JC 的小窝

go-ent使用教程

char
4
2025-05-21

Ent使用教程(优化和更新版)

什么是Ent?

Ent是一个简单而强大的Go语言实体框架,旨在简化具有大型数据模型的应用程序的开发和维护。它作为一个直观的ORM(对象关系映射)解决方案,使开发者能够通过类型安全、代码生成的API高效地与数据库交互。


安装

要安装Ent,请运行以下命令:

go get entgo.io/ent/cmd/ent

注意:请确保使用Go 1.16或更高版本,以获得与Ent最新功能的最佳兼容性。


使用Ent

创建Schema

在使用Ent之前,您需要定义一个Schema,它类似于数据库表的蓝图——类似于Django中的模型。Schema指定字段、表名和关系。

CLI命令创建Schema模板

使用 ent init命令生成Schema模板:

ent init --target <目标目录路径> <模型名称1> <模型名称2> ...
  • --target:指定创建Schema模板的目录。
  • <模型名称>:模型名称必须使用驼峰命名法(例如,ClassStudent)。

例如:

ent init --target spec/schema Class Student

这将在 spec/schema目录下创建 class.gostudent.go

Schema模板示例

以下是初始模板的样子:

// spec/schema/class.go
package schema

import "entgo.io/ent"

type Class struct {
	ent.Schema
}

func (Class) Fields() []ent.Field {
	return nil
}

func (Class) Edges() []ent.Edge {
	return nil
}
// spec/schema/student.go
package schema

import "entgo.io/ent"

type Student struct {
	ent.Schema
}

func (Student) Fields() []ent.Field {
	return nil
}

func (Student) Edges() []ent.Edge {
	return nil
}

定义完整的数据结构

接下来,通过添加字段和关系来增强这些模板。

示例:Class Schema

// spec/schema/class.go
package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/schema"
	"entgo.io/ent/schema/edge"
	"entgo.io/ent/schema/field"
)

type Class struct {
	ent.Schema
}

func (Class) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").MaxLen(50).Comment("名称"),
		field.Int("level").Comment("级别"),
	}
}

func (Class) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("students", Student.Type), // 命名为"students"以更清晰地表示一对多关系
	}
}

func (Class) Annotations() []schema.Annotation {
	return []schema.Annotation{
		entsql.Annotation{Table: "class"},
	}
}

示例:Student Schema

// spec/schema/student.go
package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/schema"
	"entgo.io/ent/schema/edge"
	"entgo.io/ent/schema/field"
)

type Student struct {
	ent.Schema
}

func (Student) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").MaxLen(50).Comment("名称"),
		field.Bool("sex").Comment("性别"),
		field.Int("age").Comment("年龄"),
	}
}

func (Student) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("class", Class.Type).
			Ref("students").
			Unique().
			Required(),
	}
}

func (Student) Annotations() []schema.Annotation {
	return []schema.Annotation{
		entsql.Annotation Table: "student"},
	}
}

关键注意事项

  • Student中省略了 class_id字段,因为Ent通过边自动管理外键。
  • Class中的边重命名为 students(复数形式),以更好地反映一对多关系。

生成代码

使用以下命令从Schema生成操作代码:

ent generate --target <目标目录路径> <Schema目录路径>
  • <Schema目录路径>:包含Schema文件的目录。
  • --target <目标目录路径>:生成代码的输出目录。

例如:

ent generate --target gen/entschema spec/schema

这将在 gen/entschema中创建必要的代码。


连接数据库

gen/entschema中生成的代码包括一个 client.go文件,提供了数据库客户端。以下是两种连接方式:

1. 标准连接

// main.go
package main

import (
	"context"
	"log"
	"<项目>/gen/entschema"
)

func main() {
	URL := "mysql://user:password@tcp(localhost:3306)/dbname?parseTime=True"
	client, err := entschema.Open("mysql", URL)
	if err != nil {
		log.Fatalf("failed opening connection to mysql: %v", err)
	}
	defer client.Close()

	// 自动迁移Schema
	if err := client.Schema.Create(context.Background()); err != nil {
		log.Fatalf("failed creating schema resources: %v", err)
	}
}

注意

  • 安装MySQL驱动:go get github.com/go-sql-driver/mysql
  • URL使用DSN格式,符合现代Ent约定。

2. 使用 sql.DB

// main.go
package main

import (
	"context"
	"database/sql"
	"log"
	"<项目>/gen/entschema"
	"entgo.io/ent/dialect/sql"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	URL := "user:password@tcp(localhost:3306)/dbname?parseTime=True"
	db, err := sql.Open("mysql", URL)
	if err != nil {
		log.Fatalf("failed opening connection: %v", err)
	}
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(50)

	drv := sql.OpenDB("mysql", db)
	client := entschema.NewClient(entschema.Driver(drv))

	defer client.Close()

	// 自动迁移Schema
	if err := client.Schema.Create(context.Background()); err != nil {
		log.Fatalf("failed creating schema resources: %v", err)
	}
}

CRUD操作

为了在项目中实际使用,通常将Ent客户端初始化为全局变量。

全局Client初始化

// app/app.go
package app

import (
	"database/sql"
	"entgo.io/ent/dialect/sql"
	"<项目>/gen/entschema"
)

var EntClient *entschema.Client

func InitEntClient(URL string) error {
	db, err := sql.Open("mysql", URL)
	if err != nil {
		return err
	}
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(50)
	drv := sql.OpenDB("mysql", db)
	EntClient = entschema.NewClient(entschema.Driver(drv))
	return nil
}

main函数中调用 InitEntClient以设置全局客户端。

创建数据

ctx := context.Background()
classObj, err := app.EntClient.Class.Create().
	SetName("三班").
	SetLevel(1).
	Save(ctx)
if err != nil {
	log.Fatalf("failed creating class: %v", err)
}

studentObj, err := app.EntClient.Student.Create().
	SetClass(classObj).
	SetName("小红").
	SetSex(false).
	SetAge(12).
	Save(ctx)
if err != nil {
	log.Fatalf("failed creating student: %v", err)
}

注意:使用 Save()而不是 SaveX(),以确保在生产代码中进行适当的错误处理。

批量创建

type studentData struct {
	Name string
	Age  int
	Sex  bool
}

data := []studentData{
	{Name: "小明", Age: 12, Sex: true},
	{Name: "小刚", Age: 13, Sex: true},
	{Name: "小李", Age: 11, Sex: false},
}

bulk := make([]*entschema.StudentCreate, len(data))
for i, d := range data {
	bulk[i] = app.EntClient.Student.Create().
		SetName(d.Name).
		SetSex(d.Sex).
		SetAge(d.Age)
}

students, err := app.EntClient.Student.CreateBulk(bulk...).Save(ctx)
if err != nil {
	log.Fatalf("failed creating students: %v", err)
}

按条件查询数据

studentObj, err := app.EntClient.Student.Query().
	Where(student.AgeEQ(12)).
	First(ctx)
if err != nil {
	log.Fatalf("failed querying student: %v", err)
}

classObj, err := studentObj.QueryClass().Only(ctx)
if err != nil {
	log.Fatalf("failed querying class: %v", err)
}

注意:使用 AgeEQ(12)(现代谓词语法)和 Only()进行单对象查询并进行错误处理。

模糊查询

studentObj, err := app.EntClient.Student.Query().
	Where(student.NameContains("红")).
	First(ctx)
if err != nil {
	log.Fatalf("failed querying student: %v", err)
}

注意:Ent的 Contains谓词简化了模糊查询,无需使用原始SQL。

按条件更新数据

classObj, err := app.EntClient.Class.Create().
	SetName("一班").
	SetLevel(2).
	Save(ctx)
if err != nil {
	log.Fatalf("failed creating class: %v", err)
}

updateCount, err := app.EntClient.Student.Update().
	Where(student.NameEQ("小红")).
	SetAge(13).
	SetClass(classObj).
	Save(ctx)
if err != nil {
	log.Fatalf("failed updating student: %v", err)
}

按条件删除数据

deleteCount, err := app.EntClient.Student.Delete().
	Where(student.NameEQ("小红")).
	Exec(ctx)
if err != nil {
	log.Fatalf("failed deleting student: %v", err)
}

Ent进阶功能

联表查询

Ent使用边(edges)来定义关系,支持一对多(O2M)、一对一(O2O)和多对多(M2M)查询。

O2M(一对多)

Schema示例

// User Schema
func (User) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("orders", Order.Type),
	}
}

// Order Schema
func (Order) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("user", User.Type).
			Ref("orders").
			Unique().
			Required(),
	}
}

查询示例

// 获取用户的所有订单
orders, err := app.EntClient.User.Query().
	Where(user.IDEQ(userID)).
	QueryOrders().
	All(ctx)

// 获取用户及其订单
user, err := app.EntClient.User.Query().
	Where(user.IDEQ(userID)).
	WithOrders().
	Only(ctx)
orders := user.Edges.Orders

// 获取订单及其用户
order, err := app.EntClient.Order.Query().
	Where(order.IDEQ(orderID)).
	WithUser().
	Only(ctx)
user := order.Edges.User

O2O(一对一)

Schema示例

// User Schema
func (User) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("profile", Profile.Type).Unique(),
	}
}

// Profile Schema
func (Profile) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("user", User.Type).
			Ref("profile").
			Unique().
			Required(),
	}
}

查询方式与O2M类似,使用 QueryProfile()WithProfile()

M2M(多对多)

Schema示例(自动生成中间表)

// User Schema
func (User) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("groups", Group.Type),
	}
}

// Group Schema
func (Group) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("users", User.Type).Ref("groups"),
	}
}

查询示例

// 获取用户的所有组
groups, err := app.EntClient.User.Query().
	Where(user.IDEQ(userID)).
	QueryGroups().
	All(ctx)

// 获取组中的所有用户
users, err := app.EntClient.Group.Query().
	Where(group.IDEQ(groupID)).
	QueryUsers().
	All(ctx)

Ent会自动为M2M关系创建中间表。


其他进阶功能

  • 事务:使用 client.Tx()进行事务操作。
  • 钩子:在CRUD操作前后添加自定义逻辑。
  • 索引和约束:在Schema中定义以提高性能和数据完整性。
  • 自定义SQL:在需要时使用 ExecQuery执行原始SQL。

结论

Ent是一个强大的Go语言ORM框架。本教程涵盖了其基础知识——安装、Schema创建、CRUD操作——并介绍了联表查询等进阶功能。有关更多详细信息和更新,请参阅Ent官方文档