用 entgo 替代 gorm

一直以来,基本上 Go 的ORM都是用 GORM 。直到前段时间朋友推荐了 entgo, 尝试之后发现 entgo 是更好的选择。entgo 是 Facebook 开源的一个基于 go generate 生成的 ORM,但是并不算复杂,相比 GORM 的好处在于,GORM中,大量存在 interface{} 传参,因此很难通过编译器检查一些本不应该有的错误,而 entgo 则通过 go generate 的方式生成带类型的代码。我个人认为这是 entgo 最大的优势。

简单使用

首先需要初始化一个项目,比如 go mod init github.com/jiajunhuang/test, 然后安装 entgo:

$ go get entgo.io/ent/cmd/ent
$

接下来就可以开始定义 schema 了,首先生成一个:

$ go run entgo.io/ent/cmd/ent init User
$

这是 entgo 和 GORM 比较不一样的地方,GORM将类型放在代码中,并且会在后续直接使用, 而 entgo 由于需要生成一些数据操作,所以定义的 schema 更像是一个元信息。

接上文,生成之后,编辑 schema:

package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/schema/field"
	"entgo.io/ent/schema/index"
)

// User 定义User类型
type User struct {
	ent.Schema
}

// Fields 方法写明有什么字段
func (User) Fields() []ent.Field {
	return []ent.Field{
		field.Int("age").Positive(),
		field.String("name").Default("unknown"),
	}
}

// Edges 写明与其它schema的关系,相当于ER图中的关系
func (User) Edges() []ent.Edge {
	return nil
}

// Indexes 写明索引
func (User) Indexes() []ent.Index {
	return []ent.Index{
		// 非唯一的联合索引
		index.Fields("age", "name"),
		// 非唯一的普通索引
		index.Fields("age"),
		// 唯一索引
		index.Fields("name").Unique(),
	}
}

然后执行命令生成代码:

$ go generate ./ent
$

这个时候就可以开始导入包并且使用了。为了方便,直接看官网例子:

https://entgo.io/docs/crud

注意,里面的 where 例子中,小写的user,是要从包里导入:

import "github.com/jiajunhuang/test/env/user"

还有一点就是,如果不希望数据被修改,还可以在 Fields() 中设置 Mutation:

func (Pair) Fields() []ent.Field {
	return []ent.Field{
		field.String("pair_symbol").MaxLen(64).NotEmpty().Immutable(),
	}
}

复用方法

可以通过 Mixin 来复用一些定义:

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{},
	}
}

Migration

数据库Migration是一个很重要的事情,entgo自带了,直接在代码里:

if err := client.Schema.Create(ctx); err != nil {
    log.Fatalf("failed creating schema resources: %v", err)
}

就会自动做migration,但是默认情况下是只增不减的,如果想要能删除不存在的 索引和列,那么可以加:

package main

import (
    "context"
    "log"
    
    "<project>/ent"
    "<project>/ent/migrate"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("failed connecting to mysql: %v", err)
    }
    defer client.Close()
    ctx := context.Background()
    // Run migration.
    err = client.Schema.Create(
        ctx, 
        migrate.WithDropIndex(true),
        migrate.WithDropColumn(true), 
    )
    if err != nil {
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

当然,也可以不用它而用 go-migrate 等库。

总结

这是 entgo 的基本用法,他还有很多高级用法这里没有介绍,我觉得这篇文章还是 起一个引路人的作用,具体的还是要去官网慢慢看文档学习才有用。


Ref:


更多文章
  • Prometheus 数据类型
  • Gin源码阅读与分析
  • 如何面试-作为面试官得到的经验
  • 自己写一个容器
  • Golang(Go语言)中实现典型的fork调用
  • 软件开发之禅---大事化小,各个击破
  • 程序员的自我修养:链接,装载与库 阅读笔记
  • Redis源码阅读与分析二:双链表
  • Redis源码阅读与分析三:哈希表
  • Redis源码阅读与分析一:sds
  • Golang runtime 源码阅读与分析
  • Golang的一些坑
  • GC 垃圾回收
  • 设计一个路由
  • Go语言性能优化实战