事务(Transactions)
开始一个事务
// GenTx generates group of entities in a transaction.
func GenTx(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return fmt.Errorf("starting a transaction: %w", err)
}
hub, err := tx.Group.
Create().
SetName("Github").
Save(ctx)
if err != nil {
return rollback(tx, fmt.Errorf("failed creating the group: %w", err))
}
// Create the admin of the group.
dan, err := tx.User.
Create().
SetAge(29).
SetName("Dan").
AddManage(hub).
Save(ctx)
if err != nil {
return rollback(tx, err)
}
// Create user "Ariel".
a8m, err := tx.User.
Create().
SetAge(30).
SetName("Ariel").
AddGroups(hub).
AddFriends(dan).
Save(ctx)
if err != nil {
return rollback(tx, err)
}
fmt.Println(a8m)
// Output:
// User(id=2, age=30, name=Ariel)
// Commit the transaction.
return tx.Commit()
}
// rollback calls to tx.Rollback and wraps the given error
// with the rollback error if occurred.
func rollback(tx *ent.Tx, err error) error {
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: %v", err, rerr)
}
return err
}
在事务成功创建实体后查询边(如:a8m.QueryGroups())必须调用 Unwrap()。
Unwrap 将实体中嵌入的底层客户端状态还原为不可执行事务版本。
注意
在一个不可执行事务实体上(例如事务提交或回滚之后)调用 Unwrap() 会触发崩溃。
完整示例参见 GitHub。
事务客户端
有时你有一份 *ent.Client 可以正常运行的代码,想要修改(或包装)为用事务进行操作。这种场景下需要事务。从已有事务中获取 *ent.Client。
// WrapGen wraps the existing "Gen" function in a transaction.
func WrapGen(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
txClient := tx.Client()
// Use the "Gen" below, but give it the transactional client; no code changes to "Gen".
if err := Gen(ctx, txClient); err != nil {
return rollback(tx, err)
}
return tx.Commit()
}
// Gen generates a group of entities.
func Gen(ctx context.Context, client *ent.Client) error {
// ...
return nil
}
完整示例参见 GitHub。
最佳实践
在事务中运行回调的可复用函数:
func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
defer func() {
if v := recover(); v != nil {
tx.Rollback()
panic(v)
}
}()
if err := fn(tx); err != nil {
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: rolling back transaction: %v", err, rerr)
}
return err
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("committing transaction: %w", err)
}
return nil
}
使用:
func Do(ctx context.Context, client *ent.Client) {
// WithTx helper.
if err := WithTx(ctx, client, func(tx *ent.Tx) error {
return Gen(ctx, tx.Client())
}); err != nil {
log.Fatal(err)
}
}
钩子 Hooks
与 模式钩子 与 运行时钩子 一样,
钩子能够注册到活动的事务上,并通过 Tx.Commit 和 Tx.Rollback 执行:
func Do(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// Add a hook on Tx.Commit.
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// Code before the actual commit.
err := next.Commit(ctx, tx)
// Code after the transaction was committed.
return err
})
})
// Add a hook on Tx.Rollback.
tx.OnRollback(func(next ent.Rollbacker) ent.Rollbacker {
return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {
// Code before the actual rollback.
err := next.Rollback(ctx, tx)
// Code after the transaction was rolled back.
return err
})
})
//
// <Code goes here>
//
return err
}
隔离级别 Isolation Levels
某些驱动程序支持调整事务的隔离级别。例如,使用 sql 驱动程序时,可通过 BeginTx 方法实现此操作。
tx, err := client.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})