前幾天看到消息Facebook孵化的ORM ent轉爲正式項目,出去好奇,簡單體驗了一下,使用上本身感受比GORM好用,因而打算把官方的文檔進行整理,也算是學習一下如何使用。mysql
ent orm 須要使用entc命令進行自動代碼生成,因此須要先安裝entc:git
go get github.com/facebook/ent/cmd/entc
github
關於這個系列的全部代碼筆記都會放到sql
github.com/peanut-cc/ent_orm_notes
shell
正常狀況下應該是在本身的項目根目錄執行下面的命令,我這裏是由於後續會有多個例子,爲了讓每一個例子的內容獨立,因此這裏會在子目錄下執行該命令數據庫
entc init User
tcp
這個命令執行後生成以下目錄結構:學習
└── quick_user_example └── ent ├── generate.go └── schema └── user.go
而schema目錄下的user.go的內容也很是簡單:ui
package schema import "github.com/facebook/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 }
給schema添加字段很是簡單,只須要在生成ent/schema/user.go
的Fields
方法中添加便可,修改以後的代碼以下:this
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. // 用於給 user 表定義字段 func (User) Fields() []ent.Field { 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
自動生成代碼,執行命令後的目錄結構爲:
└── quick_user_example └── ent ├── client.go ├── config.go ├── context.go ├── ent.go ├── enttest │ └── enttest.go ├── generate.go ├── hook │ └── hook.go ├── migrate │ ├── migrate.go │ └── schema.go ├── mutation.go ├── predicate │ └── predicate.go ├── privacy │ └── privacy.go ├── runtime │ └── runtime.go ├── runtime.go ├── schema │ └── user.go ├── tx.go ├── user │ ├── user.go │ └── where.go ├── user_create.go ├── user_delete.go ├── user.go ├── user_query.go └── user_update.go
建立表並進行簡單的添加數據,和查詢數據:
package main import ( "context" "fmt" "log" _ "github.com/go-sql-driver/mysql" "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent" "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent/user" ) func main() { client, err := ent.Open("mysql", "root:123456@tcp(192.168.1.104:3306)/ent_orm?parseTime=True") if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background() // run the auto migration tool if err := client.Schema.Create(ctx); err != nil { log.Fatalf("failed creating schema resources:%v", err) } CreateUser(ctx, client) peanut, err := QueryUser(ctx, client) if err != nil { log.Fatalln(err) } log.Fatalf("query user name is:%v, aget is %v", peanut.Name, peanut.Age) } // CreateUser 建立用戶 name=peanut, age=18 func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) { u, err := client.User. Create(). SetAge(18). SetName("peanut"). Save(ctx) if err != nil { return nil, fmt.Errorf("failed creating user: %v", err) } log.Println("user was created: ", u) return u, nil } // QueryUser 查詢用戶 where name=peanut func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) { u, err := client.User. Query(). Where(user.NameEQ("peanut")). // `Only` fails if no user found, // or more than 1 user returned. Only(ctx) if err != nil { return nil, fmt.Errorf("failed querying user: %v", err) } log.Println("user returned: ", u) return u, nil }
仍是用一樣的方法建立Car和Group 的schema
entc init Car Group
分別給ent/schema
目錄下的car.go
和group.go
添加對應的字段信息
car.go
文件:
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/field" ) // Car holds the schema definition for the Car entity. type Car struct { ent.Schema } // Fields of the Car. func (Car) Fields() []ent.Field { return []ent.Field{ field.String("model"), field.Time("registered_at"), } } // Edges of the Car. func (Car) Edges() []ent.Edge { return nil }
group.go
文件:
package schema import ( "regexp" "github.com/facebook/ent" "github.com/facebook/ent/schema/field" ) // Group holds the schema definition for the Group entity. type Group struct { ent.Schema } // Fields of the Group. func (Group) Fields() []ent.Field { return []ent.Field{ field.String("name"). // regexp validation for group name. Match(regexp.MustCompile("[a-zA-Z_]+$")), } } // Edges of the Group. func (Group) Edges() []ent.Edge { return nil }
在ent orm 中給表之間創建關係是經過Edges方法實現的,咱們更改ent/schema/user.go
中的Edges方法:
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. // 用於給 user 表定義字段 func (User) Fields() []ent.Field { return []ent.Field{ field.Int("age"). Positive(), field.String("name").Default("unknown"), } } // Edges of the User. // 和Cars表創建關係 func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("cars", Car.Type), } }
執行go generate ./ent
自動生成代碼,而後從新生成一下表結構
而後在數據中執行show create table ent_orm.cars
查看錶的詳細結構語句:
CREATE TABLE `cars` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `model` varchar(255) COLLATE utf8mb4_bin NOT NULL, `registered_at` timestamp NULL DEFAULT NULL, `user_cars` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `cars_users_cars` (`user_cars`), CONSTRAINT `cars_users_cars` FOREIGN KEY (`user_cars`) REFERENCES `users` (`id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
能夠看出,經過在印記功能創建的外鍵關係
// CreateCars 建立 Tesla 和Ford 汽車,加該汽車屬於user: peanut_pg func CreateCars(ctx context.Context, client *ent.Client) (*ent.User, error) { // creating new car with model "Tesla". tesla, err := client.Car. Create(). SetModel("Tesla"). SetRegisteredAt(time.Now()). Save(ctx) if err != nil { return nil, fmt.Errorf("failed creating car: %v", err) } // creating new car with model "Ford". ford, err := client.Car. Create(). SetModel("Ford"). SetRegisteredAt(time.Now()). Save(ctx) if err != nil { return nil, fmt.Errorf("failed creating car: %v", err) } log.Println("car was created: ", ford) // create a new user, and add it the 2 cars. peanut_pg, err := client.User. Create(). SetAge(18). SetName("peanut_pg"). // AddCars 將車屬於user peanut_pg AddCars(tesla, ford). Save(ctx) if err != nil { return nil, fmt.Errorf("failed creating user: %v", err) } log.Println("user was created: ", peanut_pg) return peanut_pg, nil }
代碼內容以下:
package main import ( "context" "fmt" "log" "time" _ "github.com/go-sql-driver/mysql" "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent" "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent/car" "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent/user" ) func main() { client, err := ent.Open("mysql", "root:123456@tcp(192.168.1.104:3306)/ent_orm?parseTime=True") if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background() // run the auto migration tool if err := client.Schema.Create(ctx); err != nil { log.Fatalf("failed creating schema resources:%v", err) } peanut_pg, err := QueryUserByName(ctx, client, "peanut_pg") if err != nil { log.Fatalln(err) } QueryCars(ctx, peanut_pg) } // QueryUserByName 經過name 查詢 func QueryUserByName(ctx context.Context, client *ent.Client, name string) (*ent.User, error) { u, err := client.User. Query(). Where(user.NameEQ(name)). // `Only` fails if no user found, // or more than 1 user returned. Only(ctx) if err != nil { return nil, fmt.Errorf("failed querying user: %v", err) } log.Println("user returned: ", u) return u, nil } // QueryCars 查詢用戶peanut_pg是否有Ford 這個車 func QueryCars(ctx context.Context, peanut_pg *ent.User) error { cars, err := peanut_pg.QueryCars().All(ctx) if err != nil { return fmt.Errorf("failed querying user cars: %v", err) } log.Println("returned cars:", cars) // what about filtering specific cars. ford, err := peanut_pg.QueryCars(). Where(car.ModelEQ("Ford")). Only(ctx) if err != nil { return fmt.Errorf("failed querying user cars: %v", err) } log.Println(ford) return nil }
在日常的查詢中咱們還會常常用到一些反向查詢,如咱們想要查詢這個車所屬的用戶是誰,這個時候須要修改
ent/schema/car.go
中的Edges方法:
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // Car holds the schema definition for the Car entity. type Car struct { ent.Schema } // Fields of the Car. func (Car) Fields() []ent.Field { return []ent.Field{ field.String("model"), field.Time("registered_at"), } } // Edges of the Car. func (Car) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). // create an inverse-edge called "owner" of type `User` // and reference it to the "cars" edge (in User schema) // explicitly using the `Ref` method. Ref("cars"). // setting the edge to unique, ensure // that a car can have only one owner. Unique(), } }
先經過QueryCarByModel
查詢一個Model=Tesla的汽車,而後經過QueryCarUser
查看這個汽車的所屬者是誰
// QueryCarByModel 查詢car.model=Tesla func QueryCarByModel(ctx context.Context, client *ent.Client) (*ent.Car, error) { car, err := client.Car.Query(). Where(car.ModelEQ("Tesla")). Only(ctx) if err != nil { return nil, fmt.Errorf("failed query car") } return car, nil } // QueryCarUser 查詢car.model=Tesla的所屬者是誰 func QueryCarUser(ctx context.Context, car *ent.Car) error { owner, err := car.QueryOwner().Only(ctx) if err != nil { return fmt.Errorf("failed querying car %q owner:%v", car.Model, err) } log.Printf("car %q owner: %q\n", car.Model, owner.Name) return nil }
在上面的關係上再添加一個用戶和組的關係,分別修改ent/schema/user.go
和ent/schema/car.go
的Edges方法
// Edges of the User. // 和Cars表創建關係 func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("cars", Car.Type), // create an inverse-edge called "groups" of type `Group` // and reference it to the "users" edge (in Group schema) // explicitly using the `Ref` method. edge.From("groups", Group.Type). Ref("users"), } }
// Edges of the Car. func (Car) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). // create an inverse-edge called "owner" of type `User` // and reference it to the "cars" edge (in User schema) // explicitly using the `Ref` method. Ref("cars"). // setting the edge to unique, ensure // that a car can have only one owner. Unique(), } }
執行go generate ./ent
自動生成代碼
經過以下方法生成基礎數據:
// CreateGraph 建立基礎數據 func CreateGraph(ctx context.Context, client *ent.Client) error { // first, create the users. a8m, err := client.User. Create(). SetAge(30). SetName("Ariel"). Save(ctx) if err != nil { return err } neta, err := client.User. Create(). SetAge(28). SetName("Neta"). Save(ctx) if err != nil { return err } // then, create the cars, and attach them to the users in the creation. _, err = client.Car. Create(). SetModel("TeslaY"). SetRegisteredAt(time.Now()). // ignore the time in the graph. SetOwner(a8m). // attach this graph to Ariel. Save(ctx) if err != nil { return err } _, err = client.Car. Create(). SetModel("TeslaX"). SetRegisteredAt(time.Now()). // ignore the time in the graph. SetOwner(a8m). // attach this graph to Ariel. Save(ctx) if err != nil { return err } _, err = client.Car. Create(). SetModel("TeslaS"). SetRegisteredAt(time.Now()). // ignore the time in the graph. SetOwner(neta). // attach this graph to Neta. Save(ctx) if err != nil { return err } // create the groups, and add their users in the creation. _, err = client.Group. Create(). SetName("GitLab"). AddUsers(neta, a8m). Save(ctx) if err != nil { return err } _, err = client.Group. Create(). SetName("GitHub"). AddUsers(a8m). Save(ctx) if err != nil { return err } log.Println("The graph was created successfully") return nil }
三種查詢例子
// QueryGithub 查詢group = GitHub 的用戶的全部的汽車 func QueryGithub(ctx context.Context, client *ent.Client) error { cars, err := client.Group. Query(). Where(group.Name("GitHub")). QueryUsers(). QueryCars(). All(ctx) if err != nil { return fmt.Errorf("failed getting cars:%v", err) } // cars returned: [Car(id=3, model=TeslaY, registered_at=Tue Aug 25 00:43:55 2020) Car(id=4, model=TeslaX, registered_at=Tue Aug 25 00:43:55 2020)] log.Println("cars returned:", cars) return nil }
func QueryArielCars(ctx context.Context, client *ent.Client) error { // Get "Ariel" from previous steps. a8m := client.User. Query(). Where( user.HasCars(), user.Name("Ariel"), ). OnlyX(ctx) cars, err := a8m. // Get the groups, that a8m is connected to: QueryGroups(). // (Group(Name=GitHub), Group(Name=GitLab),) QueryUsers(). // (User(Name=Ariel, Age=30), User(Name=Neta, Age=28),) QueryCars(). // Where( // car.Not( // Get Neta and Ariel cars, but filter out car.ModelEQ("TeslaX"), // those who named "Mazda" ), ). All(ctx) if err != nil { return fmt.Errorf("failed getting cars: %v", err) } log.Println("cars returned:", cars) // Output: (Car(Model=Tesla, RegisteredAt=<Time>), Car(Model=Ford, RegisteredAt=<Time>),) return nil }
// QueryGroupWithUsers 查詢全部由用戶的組 func QueryGroupWithUsers(ctx context.Context, client *ent.Client) error { groups, err := client.Group. Query(). Where(group.HasUsers()). All(ctx) if err != nil { return fmt.Errorf("failed getting groups: %v", err) } log.Println("groups returned:", groups) // Output: (Group(Name=GitHub), Group(Name=GitLab),) return nil }