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模板的目录。<模型名称>
:模型名称必须使用驼峰命名法(例如,Class
、Student
)。
例如:
ent init --target spec/schema Class Student
这将在 spec/schema
目录下创建 class.go
和 student.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:在需要时使用
Exec
或Query
执行原始SQL。
结论
Ent是一个强大的Go语言ORM框架。本教程涵盖了其基础知识——安装、Schema创建、CRUD操作——并介绍了联表查询等进阶功能。有关更多详细信息和更新,请参阅Ent官方文档。