id: transactions

title: Transactions

开始事务

  1. // GenTx 在一个事务中创建多个群组的实体及其用户
  2. func GenTx(ctx context.Context, client *ent.Client) error {
  3. tx, err := client.Tx(ctx)
  4. if err != nil {
  5. return fmt.Errorf("starting a transaction: %v", err)
  6. }
  7. hub, err := tx.Group.
  8. Create().
  9. SetName("Github").
  10. Save(ctx)
  11. if err != nil {
  12. return rollback(tx, fmt.Errorf("failed creating the group: %v", err))
  13. }
  14. // 为群组添加一个用户
  15. dan, err := tx.User.
  16. Create().
  17. SetAge(29).
  18. SetName("Dan").
  19. AddManage(hub).
  20. Save(ctx)
  21. if err != nil {
  22. return rollback(tx, err)
  23. }
  24. // 创建一个用户 "Ariel".
  25. a8m, err := tx.User.
  26. Create().
  27. SetAge(30).
  28. SetName("Ariel").
  29. AddGroups(hub).
  30. AddFriends(dan).
  31. Save(ctx)
  32. if err != nil {
  33. return rollback(tx, err)
  34. }
  35. fmt.Println(a8m)
  36. // Output:
  37. // User(id=2, age=30, name=Ariel)
  38. // 提交事务
  39. return tx.Commit()
  40. }
  41. // rollback 会调用 tx.Rollback,如果此时发生错误,rollback 会将该错误也包装进去。
  42. func rollback(tx *ent.Tx, err error) error {
  43. if rerr := tx.Rollback(); rerr != nil {
  44. err = fmt.Errorf("%v: %v", err, rerr)
  45. }
  46. return err
  47. }

完整的例子请查看 GitHub.

事务客户端

Transactional Client,事务客户端。 有时候,你的现有代码已经使用了 *ent.Client,但是你想将他修改(或包装)为使用事务客户端实现。 对于这种情况:你可以从现有的事务客户端获取一个 *ent.Client

  1. // WrapGen 函数将现有的 "Gen" 函数包装成事务
  2. func WrapGen(ctx context.Context, client *ent.Client) error {
  3. tx, err := client.Tx(ctx)
  4. if err != nil {
  5. return err
  6. }
  7. txClient := tx.Client()
  8. //下面会调用 "Gen",但是传输一个事务客户端给它;这样就不用修改 "Gen" 函数的代码。
  9. if err := Gen(ctx, txClient); err != nil {
  10. return rollback(tx, err)
  11. }
  12. return tx.Commit()
  13. }
  14. // Gen 函数用于创建一个群组实体。
  15. func Gen(ctx context.Context, client *ent.Client) error {
  16. // ...
  17. return nil
  18. }

完整的例子请查看 GitHub.

最佳实践

在事务中运行回调函数:

  1. func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
  2. tx, err := client.Tx(ctx)
  3. if err != nil {
  4. return err
  5. }
  6. defer func() {
  7. if v := recover(); v != nil {
  8. tx.Rollback()
  9. panic(v)
  10. }
  11. }()
  12. if err := fn(tx); err != nil {
  13. if rerr := tx.Rollback(); rerr != nil {
  14. err = errors.Wrapf(err, "rolling back transaction: %v", rerr)
  15. }
  16. return err
  17. }
  18. if err := tx.Commit(); err != nil {
  19. return errors.Wrapf(err, "committing transaction: %v", err)
  20. }
  21. return nil
  22. }

用法:

  1. func Do(ctx context.Context, client *ent.Client) {
  2. // WithTx helper.
  3. if err := WithTx(ctx, client, func(tx *ent.Tx) error {
  4. return Gen(ctx, tx.Client())
  5. }); err != nil {
  6. log.Fatal(err)
  7. }
  8. }