1. 程式人生 > >ent orm筆記1---快速嚐鮮

ent orm筆記1---快速嚐鮮

前幾天看到訊息Facebook孵化的ORM ent轉為正式專案,出去好奇,簡單體驗了一下,使用上自己感覺比GORM好用,於是打算把官方的文件進行整理,也算是學習一下如何使用。 ## 安裝 ent orm 需要使用entc命令進行自動程式碼生成,所以需要先安裝entc: `go get github.com/facebook/ent/cmd/entc` 關於這個系列的所有程式碼筆記都會放到 `github.com/peanut-cc/ent_orm_notes` ## 快速使用 ### 建立schema 正常情況下應該是在自己的專案根目錄執行下面的命令,我這裡是因為後續會有多個例子,為了讓每個例子的內容獨立,所以這裡會在子目錄下執行該命令 `entc init User` 這個命令執行後生成如下目錄結構: ```shell └── quick_user_example └── ent ├── generate.go └── schema └── user.go ``` 而schema目錄下的user.go的內容也非常簡單: ```go 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新增欄位 給schema新增欄位非常簡單,只需要在生成`ent/schema/user.go`的`Fields`方法中新增即可,修改之後的程式碼如下: ```go 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` 自動生成程式碼,執行命令後的目錄結構為: ```shell └── 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 ``` ### 建立表到資料庫 建立表並進行簡單的新增資料,和查詢資料: ```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`檔案: ```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`檔案: ```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方法: ```go 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` 查看錶的詳細結構語句: ```sql 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 ``` 可以看出,通過在印記功能建立的外來鍵關係 ### 外來鍵的資料新增 ```go // 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 } ``` ### 外來鍵的查詢 程式碼內容如下: ```go 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方法: ```go 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方法 ```go // 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"), } } ``` ```go // 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` 自動生成程式碼 通過如下方法生成基礎資料: ```go // 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 } ``` **三種查詢例子** ```go // 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 } ``` ```go 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=