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

syncd發表於2020-08-25

前幾天看到訊息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

這個命令執行後生成如下目錄結構:

└── quick_user_example
    └── ent
        ├── generate.go
        └── schema
            └── user.go

而schema目錄下的user.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.goFields方法中新增即可,修改之後的程式碼如下:

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.gogroup.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.goent/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
}

延伸閱讀

相關文章