go语言ORM框架ent使用教程

ent是什么

ent是一个简单而又功能强大的Go语言实体框架,ent易于构建和维护应用程序与大数据模型。

简而言之,ent是一款便于操作的orm框架

installation

go get entgo.io/ent/cmd/ent

使用

创建schema

在连接ent之前,我们首先需要创建schema,创建schema的作用类似django创建model,规定数据表字段,定义表名等

cli创建model模板命令

ent init --target <target dirpath> <Model Name>

--target目的是指定创建模板路径, Model Name必须使用驼峰命名法

我们可以通过ent init --target spec/schema Class Student命令在spec/schema目录下创建两个模板,class.go和student.go

如下所示:

// spec/schema/class.go

package schema

import (
	"entgo.io/ent"
)

// Class holds the schema definition for the Class entity.
type Class struct {
    
    
	ent.Schema
}

// Fields of the Class.
func (Class) Fields() []ent.Field {
    
    
	return nil
}

// Edges of the Class.
func (Class) Edges() []ent.Edge {
    
    
	return nil
}
// spec/schema/student.go

package schema

import (
	"entgo.io/ent"
)

// Student holds the schema definition for the Student entity.
type Student struct {
    
    
	ent.Schema
}

// Fields of the Student.
func (Student) Fields() []ent.Field {
    
    
	return nil
}

// Edges of the Student.
func (Student) Edges() []ent.Edge {
    
    
	return nil
}

创建完整的数据结构

我们在生成的模板内,将表字段添加进去

// spec/schema/class.go

package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/dialect/entsql"
	"entgo.io/ent/schema"
	"entgo.io/ent/schema/edge"
	"entgo.io/ent/schema/field"
)

// Class holds the schema definition for the Class entity.
type Class struct {
    
    
	ent.Schema
}

// Fields of the Class.
func (Class) Fields() []ent.Field {
    
    
	return []ent.Field{
    
    
		field.String("name").MaxLen(50).Comment("名称"),
		field.Int("level").Comment("级别"),
	}
}

// Edges of the Class.
func (Class) Edges() []ent.Edge {
    
    
	return []ent.Edge{
    
    
		edge.To("student", Student.Type),
	}
}

func (Class) Annotations() []schema.Annotation {
    
    
	return []schema.Annotation{
    
    
		entsql.Annotation{
    
    Table: "class"},
	}
}

// spec/schema/student.go

package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/dialect/entsql"
	"entgo.io/ent/schema"
	"entgo.io/ent/schema/edge"
	"entgo.io/ent/schema/field"
)

// Student holds the schema definition for the Student entity.
type Student struct {
    
    
	ent.Schema
}

// Fields of the Student.
func (Student) Fields() []ent.Field {
    
    
	return []ent.Field{
    
    
		field.String("name").MaxLen(50).Comment("名称"),
		field.Bool("sex").Comment("性别"),
		field.Int("age").Comment("年龄"),
		field.Int("class_id").Comment("班级ID"),
	}
}

// Edges of the Student.
func (Student) Edges() []ent.Edge {
    
    
	return []ent.Edge{
    
    
		edge.From("class", Class.Type).
			Ref("student").
			Unique().
			Field("class_id").
			Required(),
	}
}

func (Student) Annotations() []schema.Annotation {
    
    
	return []schema.Annotation{
    
    
		entsql.Annotation{
    
    
			Table:       "student",
		},
	}
}

生成代码

基于schema的定义(字段,索引,边,配置)来生成对应的数据表操作代码

ent generate --target <target dirpath> <template dirpath>

template dirpath目的是指定模板所在路径,根据所在目录下的模板来生成对应的代码

--target <target dirpath?目的是指定生成操作代码的路径

使用ent generate --target gen/entschema spec/schema将我们的模板生成实际的操作代码到gen/entschema路径下

ent连接数据库

之前我们在gen/entschema目录下生成了操作代码,可以看到在其中有一个client.go的文件,它就是我们连接数据库的操作文件,我们需要先创建模型生成代码之后再连接数据库的原因也在于此

常规方式连接数据库

// main.go

package main

import (
    "context"
    "log"

    "<project>/gen/entschema"
)

func main() {
    
    
    // url example: username:password@(ipAddress)/databaseName?charset=utf8ma4&parseTime=true
    URL := "root:123456@(127.0.0.1)/test?charset=utf8mb4&parseTime=true"
    
    client, err := entschema.Open("mysql", URL)
    if err != nil {
    
    
        log.Fatalf("failed opening connection to sqlite: %v", err)
    }
    defer client.Close()
    // Run the auto migration tool.
    if err := client.Schema.Create(context.Background()); err != nil {
    
    
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

整合使用sql.DB的方式连接数据库

// main.go

package main

import (
    "context"
    "log"
    "database/sql"

	_ "github.com/go-sql-driver/mysql" // 必要导入
    entsql "entgo.io/ent/dialect/sql"
    "<project>/gen/entschema"
)

func main() {
    
    
    // URL example: username:password@(ipAddress)/databaseName?charset=utf8ma4&parseTime=true
    URL := "root:123456@(127.0.0.1)/test?charset=utf8mb4&parseTime=true"
    
    var db *sql.DB
	db, err := sql.Open("mysql", URL)

	if err != nil {
    
    
		return err
	}
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(50)
	drv := entsql.OpenDB("mysql", db)
	client = entschema.NewClient(schema.Driver(drv))

    defer client.Close()
    // Run the auto migration tool.
    if err := client.Schema.Create(context.Background()); err != nil {
    
    
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

CRUD

上面介绍了数据库的连接,在实际使用中,我们往往并非在一个文件中编写所有的逻辑,所以需要将数据库的连接->client放在全局变量中

// app/app.go

package app

import (
	"database/sql"
	entsql "entgo.io/ent/dialect/sql"
	schema "goZeroApp/zerodemo/gen/entschema"
	"goZeroApp/zerodemo/internal/config"
)

var EntClient *schema.Client

func InitExtensions(URL string) error {
    
    
	var db *sql.DB
	db, err := sql.Open("mysql", URL)

	if err != nil {
    
    
		return err
	}
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(50)
	drv := entsql.OpenDB("mysql", db)
	EntClient = schema.NewClient(schema.Driver(drv))
	return nil
}

我们只需要在main方法中运行的时候执行InitExtensions方法就可以在之后的操作中使用app.EntClient来获取到数据库的client

创建数据

以之前我们创建的Class和Student表为例,我们可以在任意文件中使用如下代码

package globle

import (
	"context"
	"<package>/app"
)

ctx := context.Background()
classObj, err := app.EntClient.Class.Create().SetName("三班").SetLevel(1).Save(ctx)

studentObj := app.EntClient.Student.Create().
	SetClass(classObj).
	SetName("小红").
	SetSex(false).
	SetAge(12).
	SaveX(ctx)

可以看到在保存的时候我们有两种方式Save()SaveXSaveX()内部实际上也是调用的Save(),但区别在于SaveX()不会返回error类型,通常我们只会在单元测试用使用带X的方法以保证我们的业务代码更加的健壮。

另外,由于我们在创建模型的时候使用了边(edge)为student和class创建了外键关联关系,因此我们可以在创建Student对象的时候使用SetClass(classObj)来关联Class对象

批量创建数据

package globle

import (
    "context"
    "<package>/app"
)

type studentData struct {
    
    
    Name string
    Age  int
    Sex  bool
}

data := make([]studentData, 3)

data[0].Name = "小明"
data[1].Name = "小刚"
data[2].Name = "小李"

data[0].Age = 12
data[1].Age = 13
data[2].Age = 11

data[0].Sex = true
data[1].Sex = true
data[2].Sex = false

bulk := make([]*entschema.StudentCreate, len(data))
for i, d := range data{
    
    
	bulk[i] := app.EntClient.Student.Create().SetName(d.Name).SetSex(d.Sex).SetAge(d.Age)
}

students, err := app.EntClient.Student.CreateBulk(bulk...).Save(ctx)

Create()的对象不立马保存,而是储存在切片中,之后再统一使用CreateBulk()的方法来批量创建数据

条件查询数据

package globle

import (
    "context"
    "<package>/app"
    "<package>/gen/entschema/student"
)

ctx := context.Background()
studentObj, err := app.EntClient.Student.Query().Where(student.Age(12)).First(ctx)
classObj := studentObj.QueryClass().FirstX(ctx)

FirstX()First()的关系和SaveX()Save()的关系相似,一个不返回error类型,一个则会返回它

在外键关联的情况下,我们可以通过Query后面接实体类名称的方式来关联查询另一张表的数据,我们可以在后面接Query()方法后面能接的所有方法

模糊查询数据

package globle

import (
    "context"
    "<package>/app"
    "<package>/gen/entschema/student"
    
    "entgo.io/ent/dialect/sql"
)

ctx := context.Background()
studentObj, err := app.EntClient.Student.Query().
	Where(func(selector *sql.Selector) {
    
    
	    selector.Where(sql.Like(selector.C(student.FieldName), "%红%"))
	}).
	First(ctx)
classObj := studentObj.QueryClass().FirstX(ctx)

可以通过Where方法中使用sql.Selector函数来间接达成模糊查询的作用

条件更新数据

package globle

import (
	"context"
	"<package>/app"
	"<package>/gen/entschema/student"
)

ctx := context.Background()
classObj, err := app.EntClient.Class.Create().SetName("一班").SetLevel(2).Save(context.Background())
updateCount, err := app.EntClient.Student.Update().Where(student.Name("小红")).SetAge(13).SetClass(classObj).Save(ctx)

假如时间过了一年,小红年龄增长一岁,并且年纪从一年级到了二年级,并且班级由三班变为一班,我们可以通过Update()Where()方法来条件修改她的属性,同时由于有外键关联关系,所以我们可以使用Set后面跟Class对象名的方式来更新class表数据的变更

条件删除数据

package globle

import (
	"context"
	"<package>/app"
	"<package>/gen/entschema/student"
)

ctx := context.Background()
deleteCount, err = app.EntClient.Student.Delete().Where(student.Name("小红")).Exec(ctx)

我们可以通过Delete()Where()方法来条件删除一些数据,方法与上面的例子类似

猜你喜欢

转载自blog.csdn.net/a914541185/article/details/121274321