1. 模型与字段基础
1.1. 模型定义入门
在ORM框架中,模型用于描述应用程序中的实体类型与数据库表之间的映射关系。模型定义了实体的属性和关系,以及与它们相关的数据库特定配置。在ent框架中,模型通常用于描述一个图中的实体类型,比如User
或Group
。
模型的定义通常包括对实体的字段(或属性)和边缘(或关系)的描述,以及一些数据库特定选项。这些描述可以帮助我们定义实体的结构、属性和关系,并且可以根据模型生成相应的数据库表结构。
1.2. 字段简介
字段是模型中表示实体属性的部分。它们定义了实体的属性,如名称、年龄、日期等。在ent框架中,字段的类型包括各种基本数据类型,如整型、字符串、布尔型、时间等,以及一些SQL特定的类型,如UUID、[]byte、JSON等。
下表展示了ent框架支持的字段类型:
类型 | 描述 |
---|---|
int | 整数类型 |
uint8 | 8位无符号整数类型 |
float64 | 浮点数类型 |
bool | 布尔类型 |
string | 字符串类型 |
time.Time | 时间类型 |
UUID | UUID类型 |
[]byte | 字节数组类型(仅限SQL数据库) |
JSON | JSON类型(仅限SQL数据库) |
Enum | 枚举类型(仅限SQL数据库) |
Other | 其他类型(例如Postgres Range) |
2. 字段属性详解
2.1. 数据类型
实体模型中的属性或字段,其数据类型决定了可以保存的数据形式。这是在模型定义时非常关键的部分,下面列举了一些在ent
框架中常用的数据类型。
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// User schema.
type User struct {
ent.Schema
}
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age"), // 整数类型
field.String("name"), // 字符串类型
field.Bool("active"), // 布尔类型
field.Float("score"), // 浮点数类型
field.Time("created_at"), // 时间戳类型
}
}
-
int
:表示整数值,可以是int8
,int16
,int32
,int64
等。 -
string
:表示字符串数据。 -
bool
:表示布尔值,通常用于标志位。 -
float64
:表示浮点数,也可使用float32
。 -
time.Time
:表示时间,通常用于时间戳或日期数据。
这些字段类型将被映射到底层数据库支持的相应类型。同时,ent
支持更多复杂类型,如UUID
、JSON
、枚举(Enum
)以及对特殊数据库类型如[]byte
(仅SQL)、Other
(仅SQL)的支持。
2.2. 默认值
字段可以配置默认值,如果在创建实体时没有指定相应的值,将会使用默认值。默认值可以是固定值,也可以是一个函数生成的动态值。使用.Default
方法来设置静态默认值,或者.DefaultFunc
来设置由函数生成的默认值。
// User schema.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now), // 固定的默认值 time.Now
field.String("role").
Default("user"), // 字符串常量
field.Float("score").
DefaultFunc(func() float64 {
return 10.0 // 由函数生成的默认值
}),
}
}
2.3. 字段可选性与零值
字段默认是必需的,要声明一个可选字段,使用.Optional()
方法。可选字段在数据库中会声明为可空(nullable)字段。Nillable
选项使得字段可以显式地设置为nil
,从而区分字段的零值和未设置的状态。
// User schema.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("nickname").Optional(), // 可选字段不是必须的
field.Int("age").Optional().Nillable(), // Nillable 字段可以为 nil
}
}
在使用上述定义的模型时,age
字段既可以接受nil
值表示未设置,也可以接受非nil
的零值。
2.4. 字段唯一性
唯一字段可确保数据库表中不会有重复的值。使用Unique()
方法定义唯一字段。当确立数据完整性是关键需求时,例如用户的邮箱或用户名,就应该使用唯一字段。
// User schema.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("email").Unique(), // 唯一字段,用于避免重复的电子邮件地址
}
}
这将在底层数据库中创建一个唯一约束,以防止插入重复的值。
2.5 字段索引
字段索引用于提升数据库查询的性能,尤其在大型数据库中效果显著。在ent
框架中,可使用.Indexes()
方法创建索引。
import "entgo.io/ent/schema/index"
// User schema.
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("email"), // 在'email'字段上创建索引
index.Fields("name", "age").Unique(), // 创建复合唯一索引
}
}
索引可用于频繁查询的字段,但要注意过多的索引可能导致写操作性能下降,因此应该根据实际情况权衡是否建立索引。
2.6. 自定义标签
在ent
框架中,你可以使用StructTag
方法给生成的实体结构体字段添加自定义的标签。这些标签对于实现如JSON编码和XML编码等操作非常有用。在下面的示例中,我们将为name
字段添加自定义的JSON和XML标签。
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
// 通过StructTag方法添加自定义标签
// 这里将name字段的JSON标签设置为username,并在字段为空时忽略(omitempty)
// 同时为XML编码设置标签为name
StructTag(`json:"username,omitempty" xml:"name"`),
}
}
当通过JSON或XML进行编码时,omitempty
选项表示如果name
字段为空,那么这个字段会在编码结果中被省略掉。这对于在编写APIs时减少响应体的大小非常有用。
这里还演示了如何为同一个字段同时设置多个标签,JSON标签使用json
键,XML标签使用xml
键,它们通过空格分隔。这些标签在解析结构体进行编码或解码时,将会被库函数如encoding/json
、encoding/xml
所使用。
3. 字段验证与约束
字段验证是数据库设计中保证数据一致性、有效性的一个重要方面。在本节中,我们将深入讨论如何在实体模型中使用内建验证器、自定义验证器以及各类约束来提升数据的完整性和质量。
3.1. 内建验证器
框架提供了一系列内建验证器,用于对不同类型的字段进行常见的数据有效性校验。使用这些内建验证器可以简化开发流程,快速为字段规定有效的数据范围或格式。
以下是一些内建字段验证器的例子:
- 数值类型的验证器:
-
Positive()
:用于验证字段的值是否为正数。 -
Negative()
:用于验证字段的值是否为负数。 -
NonNegative()
:用于验证字段的值是否为非负数。 -
Min(i)
:用于验证字段的值是否大于某个给定的最小值i
。 -
Max(i)
:用于验证字段的值是否小于某个给定的最大值i
。
-
-
string
类型的验证器:-
MinLen(i)
:用于验证字符串的最小长度。 -
MaxLen(i)
:用于验证字符串的最大长度。 -
Match(regexp.Regexp)
:用于验证字符串是否匹配给定的正则表达式。 -
NotEmpty
:用于验证字符串是否非空。
-
我们来看一个实际的代码例子,这个例子中创建了一个 User
模型,它包含了一个非负整数型的 age
字段和一个固定格式的 email
字段:
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.String("email").
Match(regexp.MustCompile(`^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$`)),
}
}
3.2. 自定义验证器
虽然内建验证器可以处理许多常见的验证需求,但有时候你可能需要更复杂的验证逻辑。在这种情况下,你可以编写自定义验证器,以满足特定的业务规则。
自定义验证器是一个接收字段值并返回 error
的函数。如果返回的 error
不为空,则表示验证失败。以下是自定义验证器的一般格式:
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("phone").
Validate(func(s string) error {
// 验证电话号码是否符合预期格式
matched, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
if !matched {
return errors.New("电话号码格式不正确")
}
return nil
}),
}
}
如上所示,我们创建了一个自定义验证器来校验电话号码的格式。
3.3. 约束(Constraints)
约束是强制数据符合特定规则的数据库对象。它们可以用来确保数据的正确性和一致性,例如防止无效数据的输入或定义数据的关系。
常见的数据库约束包括:
- 主键约束(Primary key):确保每条记录在表中是唯一的。
- 唯一约束(Unique):确保某一列或多列组合的值在表中是唯一的。
- 外键约束(Foreign key):用于定义表之间的关系,确保参照完整性。
- 检查约束(Check):确保字段值满足某个特定条件。
在实体模型中,你可以如下定义用来保持数据完整性的约束:
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("username").
Unique(), // 唯一约束,确保username在表中是唯一的。
field.String("email").
Unique(), // 唯一约束,确保email在表中是唯一的。
}
}
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type).
Unique(), // 外键约束,创建与另一用户的唯一边缘关系。
}
}
总的来说,字段验证和约束对于确保良好的数据质量和避免意外的数据错误至关重要。利用 ent
框架提供的工具能够使这一过程更加简便和可靠。