前几天看到消息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
}

延伸阅读

ent orm笔记1---快速尝鲜的更多相关文章

  1. ent orm笔记2---schema使用(上)

    在上一篇关于快速使用ent orm的笔记中,我们再最开始使用entc init User 创建schema,在ent orm 中的schema 其实就是数据库模型,在schema中我们可以通过Fiel ...

  2. ent orm笔记4---Code Generation

    在前面几篇文章中,我们经常使用的可能就是entc这个命令了,entc这个工具给带来了很多功能,这篇文章主要整理关于ent orm 中Code Generation 之前的例子中有个知识点少整理了,就是 ...

  3. ent orm笔记2---schema使用(下)

    Indexes 索引 在前两篇的文章中,其实对于索引也有一些使用, 这里来详细看一下关于索引的使用 Indexes方法可以在一个或者多个字段上设置索引,以提高数据检索的速度或者定义数据的唯一性 在下面 ...

  4. Windows 10上快速尝鲜bash on Ubuntu

    今年微软Build 2016大会最让开发人员兴奋的消息之一,就是在Windows上可以原生运行Linux bash,对于非开发人员来讲,可能不知道这意味着什么,而对于开发人员来说,意味着Windows ...

  5. 腾讯产品快速尝鲜,蓝鲸智云社区版V6.1灰度测试开启

    这周小鲸悄悄推送了社区版V6.1(二进制部署版本,包含基础套餐.监控日志套餐),没过一天就有用户来问6.1的使用问题了.小鲸大吃一鲸,原来你还是爱我的. ![请添加图片描述](https://img- ...

  6. Hugging Face发布diffuser模型AI绘画库初尝鲜!

    作者:韩信子@ShowMeAI 深度学习实战系列:https://www.showmeai.tech/tutorials/42 TensorFlow 实战系列:https://www.showmeai ...

  7. Spring-Data-JPA尝鲜:快速搭建CRUD+分页后台实例

    前言:由于之前没有接触过Hibernate框架,但是最近看一些博客深深被它的"效率"所吸引,所以这就来跟大家一起就着一个简单的例子来尝尝Spring全家桶里自带的JPA的鲜 Spr ...

  8. 【翻译】五步快速使用LINQPad尝鲜StreamInsight

    StreamInsight  学习地址:http://www.cnblogs.com/StreamInsight/archive/2011/10/26/StreamInsight-Query-Seri ...

  9. DOCKER 学习笔记6 WINDOWS版尝鲜

    前言 经过前两节的学习,我们已经可以在Dokcer 环境下部署基本的主流环境有: Springboot 后端 MYSQL 持久化数据 以及Nginx 作为反向代理 虽说服务器上面的也没啥不好,但是毕竟 ...

随机推荐

  1. 自制廉价的LED+LCD型投影仪

    文档标识符:PROJECTOR_T-D-P6 作者:DLHC 最后修改日期:2020.7.30 本文链接:https://www.cnblogs.com/DLHC-TECH/p/PROJECTOR_T ...

  2. PHP libxml_clear_errors() 函数

    定义和用法 libxml_clear_errors() 函数清空 libxml 错误缓冲. 语法 libxml_clear_errors() 实例 <?phplibxml_clear_error ...

  3. 7.3 NOI模拟赛 苹果 随机 高维前缀和

    头一次遇到高维前缀和的题目 所以赛时不太会写. \(n\cdot Mx\cdot log\)的暴力做法这里不再赘述. 容易想到随机一个数字 然后其有\(\frac{1}{2}\)的概率在答案的集合中. ...

  4. IntelliJ IDEA 控制台输出中文乱码

    IntelliJ IDEA 控制台输出中文乱码部分如图所示: 解决方法一: 1.打开IntelliJ IDEA本地安装目录中bin文件夹下的idea.exe.vmoptions和idea64.exe. ...

  5. C++中unordered_map几种按键查询比较

    unorder_map有3种常见按键查值方法. 使用头文件<unordered_map>和<iostream>,以及命名空间std. 第一种是按键访问.如果键存在,则返回键对应 ...

  6. SqlServer 查询的几种方式以及数字函数、时间函数的应用总结(回归基础)

    --语法:select * from 表名 *表示查询所有字段数据 select * from Class select * from Student select * from RankingLis ...

  7. 在Spring Boot中动态实现定时任务配置

    原文路径:https://zhuanlan.zhihu.com/p/79644891 在日常的项目开发中,往往会涉及到一些需要做到定时执行的代码,例如自动将超过24小时的未付款的单改为取消状态,自动将 ...

  8. 一张图理清 Python3 所有知识点

    如果你前几天一直有关注 GitHub Trending,那你应该会留意到「Python3 in one pic」这个开源项目. 很多人学习python,不知道从何学起.很多人学习python,掌握了基 ...

  9. 痞子衡嵌入式:了解i.MXRTxxx系列ROM API及其与i.MXRT1xxx系列的差异

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRTxxx系列ROM API设计细节. 痞子衡之前写过两篇文章 <利用i.MXRT1xxx系列ROM提供的FlexSPI ...

  10. DB2 SQL Error: SQLCODE=-1585, SQLSTATE=54048

    DB2 执行SQL报错: DB2 SQL Error: SQLCODE=-1585, SQLSTATE=54048 你建的db2数据库没有建足够大的临时表空间,新建一个足够大的临时表空间 1.创建数据 ...