メインコンテンツへスキップする

トランザクション

トランザクションの開始#

// GenTxは、1つのトランザクションでエンティティのグループを生成します。
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))
}
// グループの管理者を作成
dan, err := tx.User.
Create().
SetAge(29).
SetName("Dan").
AddManage(hub).
Save(ctx)
if err != nil {
return rollback(tx, err)
}
// "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)
// トランザクションをコミット
return tx.Commit()
}
// rollbackはtx.Rollbackを呼び出し、
// エラーが発生した場合はそれをロールバックエラーでラップします
func rollback(tx *ent.Tx, err error) error {
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: %v", err, rerr)
}
return err
}

完全な例は GitHub にあります。

トランザクションクライアント#

*ent.Clientで動作する既存のコードがあり、それをトランザクションと連動するように変更(またはラップ) したい場合があります。 これらのユースケースには、トランザクションクライアントがあります。 既存のトランザクションから取得できる*ent.Clientです。

// WrapGen はトランザクション内の既存の "Gen" 関数をラップします。
func WrapGen(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
txClient := tx.Client()
// 以下の"Gen"を使用し、トランザクションクライアントを与えます。"Gen"のコード変更は必要ありません
if err := Gen(ctx, txClient); err != nil {
return rollback(tx, err)
}
return tx.Commit()
}
// Gen は、エンティティのグループを生成します。
func Gen(ctx context.Context, client *ent.Client) error {
// ...
return nil
}

完全な例は [GitHub](https://github. com/ent/ent/tree/master/examples/traversal) にあります。

ベストプラクティス#

トランザクション内でコールバックを実行する再利用可能な関数:

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 = errors.Wrapf(err, "rolling back transaction: %v", rerr)
}
return err
}
if err := tx.Commit(); err != nil {
return errors.Wrapf(err, "committing transaction: %v", 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)
}
}

フック#

スキーマフックランタイムフックフックと同様に、フックは有効なトランザクションに登録することができ、Tx.CommitまたはTx.Rollbackで実行されます。

func Do(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// Tx.Commitにフックを追加します
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// 実際にコミットする前のコード
err := next.Commit(ctx, tx)
// トランザクションがコミットされた後のコード
return err
})
})
// Tx.Rollbackにフックを追加します
tx.OnRollback(func(next ent.Rollbacker) ent.Rollbacker {
return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {
// 実際にロールバックする前のコード
err := next.Rollback(ctx, tx)
// トランザクションがロールバックされた後のコード
return err
})
})
//
// <Code goes here>
//
return err
}