事务管理

概述

事务用于保证一组数据库操作的原子性、一致性、隔离性和持久性。Juice 提供两套事务使用方式:

  1. 手动事务(engine.Tx() / engine.ContextTx(...)

  2. 函数式事务(juice.Transaction(...) / juice.NestedTransaction(...)

事务接口

Juice 的事务相关核心接口如下:

type Manager interface {
    Object(v any) SQLRowsExecutor
}

type TxManager interface {
    Manager
    Begin() error
    Commit() error
    Rollback() error
}

手动事务

适合你需要显式控制 Begin/Commit/Rollback 的场景:

tx := engine.Tx()
if err := tx.Begin(); err != nil {
    return err
}
defer tx.Rollback()

if _, err := tx.Object(Repo{}.CreateUser).ExecContext(ctx, user); err != nil {
    return err
}

if _, err := tx.Object(Repo{}.CreateOrder).ExecContext(ctx, order); err != nil {
    return err
}

return tx.Commit()

Note

建议始终使用 defer tx.Rollback() 作为兜底,成功路径再执行 tx.Commit()

函数式事务

函数式事务会在回调中自动注入事务 manager,适合 service 层封装:

baseCtx := juice.ContextWithManager(context.Background(), engine)

err := juice.Transaction(baseCtx, func(ctx context.Context) error {
    repo := NewUserRepository()
    _, err := repo.CreateUser(ctx, user)
    return err
},
    tx.WithIsolationLevel(sql.LevelReadCommitted),
    tx.WithReadOnly(false),
)

if err != nil {
    return err
}

Transaction 的行为是:回调返回 nil 时提交,返回非 nil 时回滚。

嵌套调用语义

NestedTransaction 的语义是“已在事务中则复用当前事务,否则新开事务”:

err := juice.Transaction(baseCtx, func(ctx context.Context) error {
    if err := serviceA(ctx); err != nil {
        return err
    }

    return juice.NestedTransaction(ctx, func(ctx context.Context) error {
        return serviceB(ctx)
    })
})

Attention

NestedTransaction 不是数据库 savepoint 语义;它不会自动创建独立的内层提交点。

隔离级别与只读选项

你可以在事务开启时指定隔离级别和只读属性:

err := juice.Transaction(baseCtx, handler,
    tx.WithIsolationLevel(sql.LevelSerializable),
    tx.WithReadOnly(true),
)

这些选项与 database/sqlsql.TxOptions 对齐。

延伸阅读