搜索
首页后端开发Golang使用通用框架在 Go 中构建健壮的 SQL 事务执行

Building Robust SQL Transaction Execution in Go with a Generic Framework

在 Go 中使用 SQL 数据库时,确保原子性并在多步骤事务期间管理回滚可能具有挑战性。在本文中,我将指导您创建一个健壮、可重用且可测试的框架,用于在 Go 中执行 SQL 事务,并使用泛型来实现灵活性。

我们将构建一个 SqlWriteExec 实用程序,用于在事务中执行多个相关数据库操作。它支持无状态和有状态操作,支持复杂的工作流程,例如插入相关实体,同时无缝管理依赖关系。

为什么我们需要 SQL 事务框架?

在实际应用中,数据库操作很少是孤立的。考虑以下场景:

插入用户并自动更新其库存。
创建订单并处理付款,确保一致性。
由于涉及多个步骤,在故障期间管理回滚对于确保数据完整性至关重要。

在 Txn 管理中使用 go。

如果您正在编写数据库交易,那么在编写核心逻辑之前您可能需要考虑几个样板。虽然这个 txn 管理是由 java 中的 spring boot 管理的,并且在用 java 编写代码时你从来没有太在意这些,但在 golang 中却不是这样。下面提供了一个简单的示例

func basicTxn(db *sql.DB) error {
    // start a transaction
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
        } else if err != nil {
            tx.Rollback()
        } else {
            tx.Commit()
        }
    }()

    // insert data into the orders table
    _, err = tx.Exec("INSERT INTO orders (id, customer_name, order_date) VALUES (1, 'John Doe', '2022-01-01')")
    if err != nil {
        return err
    }
    return nil
}

我们不能指望为每个函数重复回滚/提交代码。这里我们有两个选择,要么创建一个类,该类将提供一个函数作为返回类型,该类在 defer 中执行时将提交/回滚 txn,要么创建一个包装类,它将所有 txn 函数包装在一起并一次性执行。

我选择了后面的选择,代码的更改如下所示。

func TestSqlWriteExec_CreateOrderTxn(t *testing.T) {

    db := setupDatabase()
    // create a new SQL Write Executor
    err := dbutils.NewSqlTxnExec[OrderRequest, OrderProcessingResponse](context.TODO(), db, nil, &OrderRequest{CustomerName: "CustomerA", ProductID: 1, Quantity: 10}).
        StatefulExec(InsertOrder).
        StatefulExec(UpdateInventory).
        StatefulExec(InsertShipment).
        Commit()
    // check if the transaction was committed successfully
    if err != nil {
        t.Fatal(err)
        return
    }
    verifyTransactionSuccessful(t, db)
    t.Cleanup(
        func() { 
            cleanup(db)
            db.Close() 
        },
    )
}
func InsertOrder(ctx context.Context, txn *sql.Tx, order *OrderRequest, orderProcessing *OrderProcessingResponse) error {
    // Insert Order
    result, err := txn.Exec("INSERT INTO orders (customer_name, product_id, quantity) VALUES (, , )", order.CustomerName, order.ProductID, order.Quantity)
    if err != nil {
        return err
    }
    // Get the inserted Order ID
    orderProcessing.OrderID, err = result.LastInsertId()
    return err
}

func UpdateInventory(ctx context.Context, txn *sql.Tx, order *OrderRequest, orderProcessing *OrderProcessingResponse) error {
    // Update Inventory if it exists and the quantity is greater than the quantity check if it exists
    result, err := txn.Exec("UPDATE inventory SET product_quantity = product_quantity -  WHERE id =  AND product_quantity >= ", order.Quantity, order.ProductID)
    if err != nil {
        return err
    }
    // Get the number of rows affected
    rowsAffected, err := result.RowsAffected()
    if rowsAffected == 0 {
        return errors.New("Insufficient inventory")
    }
    return err
}

func InsertShipment(ctx context.Context, txn *sql.Tx, order *OrderRequest, orderProcessing *OrderProcessingResponse) error {
    // Insert Shipment
    result, err := txn.Exec("INSERT INTO shipping_info (customer_name, shipping_address) VALUES (, 'Shipping Address')", order.CustomerName)
    if err != nil {
        return err
    }
    // Get the inserted Shipping ID
    orderProcessing.ShippingID, err = result.LastInsertId()
    return err
}

这段代码将会更加精确和简洁。

核心逻辑是如何实现的

这个想法是将 txn 隔离到单个 go 结构,以便它可以接受多个 txn。我所说的 txn 是指将使用我们为类创建的 txn 执行操作的函数。

type TxnFn[T any] func(ctx context.Context, txn *sql.Tx, processingReq *T) error
type StatefulTxnFn[T any, R any] func(ctx context.Context, txn *sql.Tx, processingReq *T, processedRes *R) error

这两个是函数类型,它们将接受 txn 来处理某些内容。现在在数据层中实现创建一个像这样的函数并将其传递给执行器类,该执行器类负责注入参数并执行该函数。

// SQL Write Executor is responsible when executing write operations
// For dependent writes you may need to add the dependent data to processReq and proceed to the next function call
type SqlTxnExec[T any, R any] struct {
    db               *sql.DB
    txn              *sql.Tx
    txnFns         []TxnFn[T]
    statefulTxnFns []StatefulTxnFn[T, R]
    processingReq    *T
    processedRes     *R
    ctx              context.Context
    err              error
}

这是我们存储所有 txn_fn 详细信息的地方,我们将使用 Commit() 方法来尝试提交 txn。

func (s *SqlTxnExec[T, R]) Commit() (err error) {
    defer func() {
        if p := recover(); p != nil {
            s.txn.Rollback()
            panic(p)
        } else if err != nil {
            err = errors.Join(err, s.txn.Rollback())
        } else {
            err = errors.Join(err, s.txn.Commit())
        }
        return
    }()

    for _, writeFn := range s.txnFns {
        if err = writeFn(s.ctx, s.txn, s.processingReq); err != nil {
            return
        }
    }

    for _, statefulWriteFn := range s.statefulTxnFns {
        if err = statefulWriteFn(s.ctx, s.txn, s.processingReq, s.processedRes); err != nil {
            return
        }
    }
    return
}

您可以在存储库中找到更多示例和测试 -
https://github.com/mahadev-k/go-utils/tree/main/examples

虽然现在我们偏向分布式系统和共识协议,但我们仍然使用sql并且它仍然存在。

如果有人愿意贡献并在此基础上继续发展,请告诉我!!
感谢您阅读本文!!
https://in.linkedin.com/in/mahadev-k-934520223
https://x.com/mahadev_k_

以上是使用通用框架在 Go 中构建健壮的 SQL 事务执行的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
GO中的接口和多态性:实现代码可重复使用性GO中的接口和多态性:实现代码可重复使用性Apr 29, 2025 am 12:31 AM

Interfaceand -polymormormormormormingingoenhancecodereusability and Maintainability.1)DewineInterfaceSattherightabStractractionLevel.2)useInterInterFacesForceFordEffeldIndentientIndoction.3)ProfileCodeTomanagePerformanceImpacts。

您如何通过Go中的地图迭代?您如何通过Go中的地图迭代?Apr 28, 2025 pm 05:15 PM

文章通过GO中的地图讨论迭代,专注于安全实践,修改条目和大型地图的性能注意事项。

您如何在GO中创建地图?您如何在GO中创建地图?Apr 28, 2025 pm 05:14 PM

本文讨论了创建和操纵GO中的地图,包括初始化方法以及添加/更新元素。

阵列和切片的GO有什么区别?阵列和切片的GO有什么区别?Apr 28, 2025 pm 05:13 PM

本文讨论了GO中的数组和切片之间的差异,重点是尺寸,内存分配,功能传递和用法方案。阵列是固定尺寸的,分配的堆栈,而切片是动态的,通常是堆积的,并且更灵活。

您如何在Go中创建切片?您如何在Go中创建切片?Apr 28, 2025 pm 05:12 PM

本文讨论了在GO中创建和初始化切片,包括使用文字,制造功能以及切片现有数组或切片。它还涵盖了切片语法并确定切片长度和容量。

您如何在Go中创建一个数组?您如何在Go中创建一个数组?Apr 28, 2025 pm 05:11 PM

本文说明了如何在GO中创建和初始化数组,讨论数组和切片之间的差异,并解决了数组的最大尺寸限制。数组与切片:固定与动态,值与参考类型。

在GO中创建结构的语法是什么?在GO中创建结构的语法是什么?Apr 28, 2025 pm 05:10 PM

文章讨论了GO中结构的语法和初始化,包括字段命名规则和结构嵌入。主要问题:如何有效地在GO编程中使用结构。(字符:159)

您如何在GO中创建指针?您如何在GO中创建指针?Apr 28, 2025 pm 05:09 PM

本文在GO中解释了创建和使用指针,讨论了诸如有效的内存使用和安全管理实践之类的好处。主要问题:安全指针使用。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。