mirror of
https://github.com/ent/ent.git
synced 2026-04-29 06:00:55 +03:00
* all: use %w instead of %v for nested errors with fmt.Errorf * all: update generated code to use %w instead of %v for error wrapping
170 lines
4.0 KiB
Markdown
Executable File
170 lines
4.0 KiB
Markdown
Executable File
---
|
|
id: transactions
|
|
title: Transactions
|
|
---
|
|
|
|
## Starting A Transaction
|
|
|
|
```go
|
|
// 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
|
|
}
|
|
```
|
|
|
|
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/traversal).
|
|
|
|
## Transactional Client
|
|
|
|
Sometimes, you have an existing code that already works with `*ent.Client`, and you want to change it (or wrap it)
|
|
to interact with transactions. For these use cases, you have a transactional client. An `*ent.Client` that you can
|
|
get from an existing transaction.
|
|
|
|
```go
|
|
// 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
|
|
}
|
|
```
|
|
|
|
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/traversal).
|
|
|
|
## Best Practices
|
|
|
|
Reusable function that runs callbacks in a transaction:
|
|
|
|
```go
|
|
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
|
|
}
|
|
```
|
|
|
|
Its usage:
|
|
|
|
```go
|
|
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
|
|
|
|
Same as [schema hooks](hooks.md#schema-hooks) and [runtime hooks](hooks.md#runtime-hooks), hooks can be registered on
|
|
active transactions, and will be executed on `Tx.Commit` or `Tx.Rollback`:
|
|
|
|
```go
|
|
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
|
|
}
|
|
```
|