Ent 基础回顾:Schema 定义、CRUD 操作、Client 与 Tx 的创建
在深入事务边界与可观测性之前,必须牢固掌握 Ent 的核心基础。本章将系统回顾 Schema 定义、CRUD 操作以及 Client 与 Tx 的创建方式,为后续事务边界的实战打下坚实的地基。
1. Schema 定义:数据模型的蓝图
Ent 的 Schema 是数据模型的声明式定义,位于 ent/schema/ 目录下。每个 Schema 对应数据库中的一张表,通过 Go 代码描述字段、索引、边(关系)和注解。
1.1 字段(Fields)
字段定义实体的属性,支持多种数据类型和可选配置:
| 字段类型 | Go 类型 | 常用配置 |
|---|---|---|
String |
string |
Unique(), NotEmpty(), Default("") |
Int |
int |
Min(0), Max(100), Default(0) |
Float |
float64 |
Precision(10,2), Default(0.0) |
Bool |
bool |
Default(false) |
Time |
time.Time |
Default(time.Now), Immutable() |
JSON |
map[string]interface{} |
Optional() |
Enum |
string |
Values("active", "inactive") |
实战要点: 使用
Immutable() 标记创建后不可修改的字段(如创建时间);使用 Annotations 为字段添加元数据,用于代码生成或可观测性标签。
1.2 边(Edges)
边定义实体之间的关联关系,支持一对一、一对多、多对多:
// ent/schema/user.go
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("posts", Post.Type),
edge.From("group", Group.Type).
Ref("users").
Unique(),
}
}
1.3 索引(Indexes)
索引用于加速查询和保证唯一性:
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("email").Unique(),
index.Fields("name", "age"),
}
}
2. CRUD 操作:数据生命周期的核心
Ent 生成的客户端 API 提供了类型安全的 CRUD 方法,无需手写 SQL。
2.1 创建(Create)
// 单条创建
u, err := client.User.
Create().
SetName("Alice").
SetAge(30).
SetEmail("alice@example.com").
Save(ctx)
// 批量创建
users, err := client.User.
CreateBulk(
client.User.Create().SetName("Bob").SetAge(25),
client.User.Create().SetName("Carol").SetAge(28),
).
Save(ctx)
2.2 查询(Query)
// 按 ID 查询
u, err := client.User.Get(ctx, 1)
// 条件查询
users, err := client.User.
Query().
Where(user.Name("Alice")).
All(ctx)
// 关联查询(Eager Loading)
users, err := client.User.
Query().
WithPosts().
All(ctx)
2.3 更新(Update)
// 单条更新
u, err := client.User.
UpdateOneID(1).
SetAge(31).
Save(ctx)
// 批量更新
n, err := client.User.
Update().
Where(user.AgeGT(30)).
SetStatus("vip").
Save(ctx)
2.4 删除(Delete)
// 单条删除
err := client.User.
DeleteOneID(1).
Exec(ctx)
// 批量删除
n, err := client.User.
Delete().
Where(user.AgeLT(18)).
Exec(ctx)
性能提示: 批量操作(
CreateBulk、Update、Delete)会合并为单条 SQL 语句,显著减少网络往返次数。
3. Client 与 Tx:事务与非事务的基石
Ent 提供两种操作模式:Client(自动提交)和 Tx(手动事务)。理解两者的区别是掌握事务边界的第一步。
3.1 Client:自动提交模式
每个 Save() 或 Exec() 调用都会立即提交到数据库,适合单条操作或无需原子性的场景。
// 创建数据库连接
client, err := ent.Open("postgres", "host=localhost port=5432 user=postgres dbname=mydb sslmode=disable")
// 自动迁移
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
// 每条 Save 都是独立事务
u1, _ := client.User.Create().SetName("Alice").Save(ctx)
u2, _ := client.User.Create().SetName("Bob").Save(ctx)
3.2 Tx:手动事务模式
使用 Tx 将多个操作包裹在同一个数据库事务中,保证原子性:
// 开启事务
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// 在事务内执行操作
u, err := tx.User.Create().SetName("Alice").Save(ctx)
if err != nil {
tx.Rollback()
return err
}
p, err := tx.Post.Create().SetTitle("Hello").SetAuthorID(u.ID).Save(ctx)
if err != nil {
tx.Rollback()
return err
}
// 提交事务
if err := tx.Commit(); err != nil {
return err
}
常见陷阱: 事务内的所有操作必须使用
tx 而非 client,否则操作会在事务外执行,导致原子性失效。务必检查每个调用的接收者是否为 tx。
3.3 Client 与 Tx 的对比
| 特性 | Client | Tx |
|---|---|---|
| 事务边界 | 单条操作自动提交 | 手动控制 Commit/Rollback |
| 原子性 | 仅单条操作 | 跨多条操作 |
| 性能 | 每条操作一次网络往返 | 批量操作合并提交 |
| 适用场景 | 简单查询、单条写入 | 多表关联写入、数据一致性要求高 |
| 可观测性 | 每条操作独立追踪 | 整个事务作为单一追踪单元 |
4. 核心流程可视化
下图展示了 Client 与 Tx 在数据库操作中的核心流程差异:
5. 事务边界实战:从 Schema 到 Tx 的完整链路
将 Schema 定义、CRUD 操作与事务模式串联起来,形成一个完整的实战链路:
- 定义 Schema:在
ent/schema/中声明字段、边和索引。 - 生成代码:运行
go generate ./ent生成类型安全的客户端 API。 - 创建 Client:通过
ent.Open()建立数据库连接。 - 执行迁移:调用
client.Schema.Create()同步表结构。 - 选择模式:根据业务需求选择 Client(自动提交)或 Tx(手动事务)。
- 执行 CRUD:使用生成的 API 进行数据操作。
- 提交/回滚:Tx 模式下调用
Commit()或Rollback()。