如何编写可重用的数据库事务包装器?

问题描述 投票:0回答:1

我需要编写一个可重用的函数,其中的一部分可以在调用之前和之后运行。

例如:

isNewTnx, rollback, beginErr := DbConnectionManager.Begin(req)

if beginErr != nil {
    return nil, beginErr
}

if isNewTnx {
    defer rollback()
}

上面的代码启动一个事务,如果它是一个新事务,它会控制它,这意味着它可以是

commit
rollback
。我被迫在很多服务中编写这段代码。有什么办法可以让它更可重用吗?我知道函数柯里化是一种选择,还有其他选择吗?

go transactions reusability
1个回答
0
投票

以下代码使用

github.com/jmoiron/sqlx
中的类型作为示例。给定一个包装数据库驱动程序的数据结构(主要用于接口实现):

type Datastore struct {
    *sqlx.DB
}

你在上面声明一个方法:

func (ds *Datastore) InTransaction(ctx context.Context, fn func(*sqlx.Tx) error) error {
    tx, err := ds.BeginTxx(ctx, nil)
    if err != nil {
        return fmt.Errorf("open tx error: %w", err)
    }
    defer tx.Rollback()

    if err := fn(tx); err != nil {
        return err
    }

    if err := tx.Commit(); err != nil {
        return fmt.Errorf("commit tx error: %w", err)
    }
    return nil
}

这里的技巧是始终使用

defer tx.Rollback()
进行回滚。如果事务成功完成,则回滚不会执行任何操作。如果发生错误,则会回滚事务。

然后你使用的是:

// ds := &Datastore{sqlx.MustConnect(.....)}

err := ds.InTransaction(ctx, func(tx *sqlx.Tx) error {
    // actual application code where you run SQL statements using `tx`
})

对于需要返回某些内容并可能出现错误的函数,您可以使用

InTransaction
函数的通用包装器。不幸的是,这必须是一个采用数据存储结构(或任何适当的接口)的顶级函数,因为方法不能具有尚未在接收器类型上定义的类型参数:

// InTransaction is an adapter function to call InTransaction with an arbitrarily typed return pair.
func InTransaction[T any](ds *Datastore, ctx context.Context, fn func(tx *sqlx.Tx) (T, error)) (T, error) {
    // this is the possibly empty typed value we want to return
    var t T
    // this is either the error produced by fn or the error produced by InTransaction
    err := e.InTransaction(ctx, func(exec ExecStrategy) error {
        result, err := fn(exec)
        if err != nil {
            return err
        }
        // propagate the typed value from fn out of InTransaction
        t = result
        return nil
    })
    // return t — possibly empty depending on what fn returned or whether it was called at all — and err
    return t, err
}

然后你将其用作:

// ds := &Datastore{sqlx.MustConnect(.....)}

err := InTransaction(ds, ctx, func(tx *sqlx.Tx) (*MyType, error) {
    // actual application code where you run SQL statements using `tx`
    // and may return an arbitrary type and error
})
© www.soinside.com 2019 - 2024. All rights reserved.