Ent 是 Facebook 开源的,一个简单而又功能强大的 Go 语言实体框架,ent 易于构建和维护应用程序与大数据模型
介绍
- 图就是代码:将任何数据库表建模为 Go 对象
- 轻松地遍历任何图形:可以轻松地运行查询、聚合和遍历任何图形结构
- 静态类型和显式 API:使用代码生成静态类型和显式 API,查询数据更加便捷
- 多存储驱动程序:支持 MySQL, PostgreSQL, SQLite 和 Gremlin
- 可扩展:简单地扩展和使用 Go 模板自定义
安装
go install entgo.io/ent/cmd/ent@latest
help
$ ent --help
Usage:
ent [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
describe print a description of the graph schema
generate generate go code for the schema directory
help Help about any command
new initialize a new environment with zero or more schemas
Flags:
-h, --help help for ent
Use "ent [command] --help" for more information about a command.
$ ent help new
initialize a new environment with zero or more schemas
Usage:
ent new [flags] [schemas]
Examples:
ent new Example
ent new --target entv1/schema User Group
ent new --template ./path/to/file.tmpl User
Flags:
-h, --help help for new
--target string target directory for schemas (default "ent/schema")
--template string template to use for new schemas
使用
# 初始化项目
mkdir entdemo && cd entdemo
go mod init entdemo
创建实体 User
ent new --target ent/schema User
entdemo/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 // 默认值
// 新增字段
return []ent.Field{
field.Int("age").
Positive(),
field.String("name").
Default("unknown"),
}
}
// Edges of the User.
func (User) Edges() []ent.Edge {
return nil
}
生成数据库实体
go generate ./ent
生成的目录结构:
$ tree .
.
├── ent
│ ├── client.go
│ ├── ent.go
│ ├── enttest
│ │ └── enttest.go
│ ├── generate.go
│ ├── hook
│ │ └── hook.go
│ ├── migrate
│ │ ├── migrate.go
│ │ └── schema.go
│ ├── mutation.go
│ ├── predicate
│ │ └── predicate.go
│ ├── runtime
│ │ └── runtime.go
│ ├── runtime.go
│ ├── schema
│ │ └── user.go
│ ├── tx.go
│ ├── user
│ │ ├── user.go
│ │ └── where.go
│ ├── user.go
│ ├── user_create.go
│ ├── user_delete.go
│ ├── user_query.go
│ └── user_update.go
├── go.mod
└── go.sum
9 directories, 22 files
初始化数据库连接
package main
import (
"context"
"log"
_ "github.com/go-sql-driver/mysql" // 导入MySQL驱动
"entdemo/ent"
)
func main() {
// client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
// 使用 ent.Open 建立与 MYSQL 数据库的连接
client, err := ent.Open("mysql", "<username>:<password>@tcp(localhost:3306)/<database>?parseTime=True")
if err != nil {
log.Fatalf("failed opening connection to mysql: %v", err)
}
defer client.Close()
// Run migration.
ctx := context.Background()
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
// err = client.Schema.Create(
// ctx,
// migrate.WithDropIndex(true),
// migrate.WithDropColumn(true),
// )
// if err != nil {
// log.Fatalf("failed creating schema resources: %v", err)
// }
// 创建用户实体
u, err := CreateUser(ctx, client)
if err != nil {
log.Fatalf("failed creating user: %v", err)
}
log.Printf("created user: %#v\n", u)
// 查询用户实体
u, err := QueryUser(ctx, client)
if err != nil {
log.Fatalf("failed querying user: %v", err)
}
log.Printf("queried user: %#v\n", u)
}
CRUD API
Create One
a8m, err = a8m.Update(). // User update builder.
RemoveGroup(g2). // Remove a specific edge.
ClearCard(). // Clear a unique edge.
SetAge(30). // Set a field value.
AddRank(10). // Increment a field value.
AppendInts([]int{1}). // Append values to a JSON array.
Save(ctx) // Save and return.
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
u, err := client.User.
Create().
SetAge(30).
SetName("a8m").
Save(ctx) // 调用 Save 将实体保存到数据库中
if err != nil {
return nil, fmt.Errorf("failed creating user: %w", err)
}
log.Println("user was created: ", u)
return u, nil
}
Create Many
pets, err := client.Pet.CreateBulk(
client.Pet.Create().SetName("pedro").SetOwner(a8m),
client.Pet.Create().SetName("xabi").SetOwner(a8m),
client.Pet.Create().SetName("layla").SetOwner(a8m),
).Save(ctx)
names := []string{"pedro", "xabi", "layla"}
pets, err := client.Pet.MapCreateBulk(names, func(c *ent.PetCreate, i int) {
c.SetName(names[i]).SetOwner(a8m)
}).Save(ctx)
查询实体
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
u, err := client.User.
Query().
Where(user.NameEQ("a8m")).
Only(ctx)
if err != nil {
return nil, fmt.Errorf("failed querying user: %w", err)
}
log.Println("user returned: ", u)
return u, nil
}
高级使用
指定表明
func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "user"},
}
}
索引
- 在
entdemo/ent/schema/user.go
中添加索引
// Indexes 写明索引
func (User) Indexes() []ent.Index {
return []ent.Index{
// 非唯一的联合索引
index.Fields("age", "name"),
// 非唯一的普通索引
index.Fields("age"),
// 唯一索引
index.Fields("name").Unique(),
}
}
复用字段
- 在
entdemo/schema/mixin.go
定义复用字段
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/mixin"
)
// -------------------------------------------------
// Mixin definition
// TimeMixin implements the ent.Mixin for sharing
// time fields with package schemas.
type TimeMixin struct {
// We embed the `mixin.Schema` to avoid
// implementing the rest of the methods.
mixin.Schema
}
func (TimeMixin) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Immutable().
Default(time.Now),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now),
field.Bool("deleted").Default(false),
}
}
// 要记得在User里增加这个方法
func (User) Mixin() []ent.Mixin {
return []ent.Mixin{
TimeMixin{},
}
}
Edge 设置
- edge 即关系,通过设置 edge 我们可以便捷的进行对象的关联查询
- 更多参考
// Edges of the User To Car, edge name is: cars
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("cars", Car.Type),
}
}
// Edges of the Car from User, edge name is: owner
func (Car) Edges() []ent.Edge {
return []ent.Edge{
// Create an inverse-edge called "owner" of type `User`
// and reference it to the "cars" edge (in User schema)
// explicitly using the `Ref` method.
edge.From("owner", User.Type).
Ref("cars").
// setting the edge to unique, ensure
// that a car can have only one owner.
Unique(),
}
}
func CreateCarsForUser(ctx context.Context, client *ent.Client, userID int) error {
user, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("failed getting user: %v", err)
return err
}
// 创建一辆新车并关联到用户
_, err = client.Car.
Create().
SetModel("Tesla").
SetRegisteredAt(time.Now()).
SetOwner(user).
Save(ctx)
if err != nil {
log.Fatalf("failed creating car for user: %v", err)
return err
}
log.Println("car was created and associated with the user")
return nil
}
func QueryUserCars(ctx context.Context, client *ent.Client, userID int) error {
user, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("failed getting user: %v", err)
return err
}
// 查询用户所拥有的所有汽车
cars, err := user.QueryCars().All(ctx)
if err != nil {
log.Fatalf("failed querying cars: %v", err)
return err
}
for _, car := range cars {
log.Printf("car: %v, model: %v", car.ID, car.Model)
}
return nil
}
Edge 可视化
- Atlas(是一个通用的数据库迁移工具,可以处理各种数据库的表结构版本管理)可以实现对 Edge 的可视化,Mac 安装:
brew install ariga/tap/atlas
连接池
package main
import (
"time"
"<your_project>/ent"
"entgo.io/ent/dialect/sql"
)
func Open() (*ent.Client, error) {
drv, err := sql.Open("mysql", "<mysql-dsn>")
if err != nil {
return nil, err
}
// Get the underlying sql.DB object of the driver.
db := drv.DB()
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(time.Hour)
return ent.NewClient(ent.Driver(drv)), nil
}