ent是一个简单而又功能强大的Go语言实体框架,ent易于构建和维护应用程序与大数据模型。
简而言之,ent是一款便于操作的orm框架
go get entgo.io/ent/cmd/ent
在连接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
template dirpath
目的是指定模板所在路径,根据所在目录下的模板来生成对应的代码
--target
使用ent generate --target gen/entschema spec/schema
将我们的模板生成实际的操作代码到gen/entschema
路径下
之前我们在gen/entschema
目录下生成了操作代码,可以看到在其中有一个client.go的文件,它就是我们连接数据库的操作文件,我们需要先创建模型生成代码之后再连接数据库的原因也在于此
常规方式连接数据库
// main.go
package main
import (
"context"
"log"
"/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"
"/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)
}
}
上面介绍了数据库的连接,在实际使用中,我们往往并非在一个文件中编写所有的逻辑,所以需要将数据库的连接->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"
"/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()
和SaveX
,SaveX()
内部实际上也是调用的Save()
,但区别在于SaveX()
不会返回error
类型,通常我们只会在单元测试用使用带X
的方法以保证我们的业务代码更加的健壮。
另外,由于我们在创建模型的时候使用了边(edge)为student和class创建了外键关联关系,因此我们可以在创建Student
对象的时候使用SetClass(classObj)
来关联Class
对象
批量创建数据
package globle
import (
"context"
"/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"
"/app"
"/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"
"/app"
"/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"
"/app"
"/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"
"/app"
"/gen/entschema/student"
)
ctx := context.Background()
deleteCount, err = app.EntClient.Student.Delete().Where(student.Name("小红")).Exec(ctx)
我们可以通过Delete()
加Where()
方法来条件删除一些数据,方法与上面的例子类似