”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用通用框架构建强大的SQL交易执行

使用通用框架构建强大的SQL交易执行

发布于2025-03-23
浏览:423

Building Robust SQL Transaction Execution in Go with a Generic Framework在使用GO中的SQL数据库时,确保原子能和在多步骤交易期间管理回滚可能具有挑战性。在本文中,我将指导您创建一个可重复使用和可测试的框架,以使用gon中的SQL Transactions,使用仿制药进行灵活性。

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

为什么我们需要SQL交易的框架?

在现实世界应用程序中,很少隔离数据库操作。考虑以下方案:

插入用户并在原子上更新其库存。

创建订单并处理其付款,确保一致性。 涉及多个步骤,在故障期间管理回滚对于确保数据完整性至关重要。

与TXN管理一起工作。

如果您正在编写数据库TXN,则在编写核心逻辑之前,可能需要考虑几个锅炉板。尽管此TXN管理由Java的Spring Boot管理,而在Java编写代码时,您从来没有打扰过这些管理,但在Golang中并非如此。下面提供了一个简单的示例

func basictxn(db *sql.db)错误{ //开始交易 TX,err:= db.begin() 如果err!= nil { 返回错误 } defer func(){ 如果r:= recover(); r!= nil { tx.rollback() } else如果err!= nil { tx.rollback() } 别的 { tx.commit() } }() //将数据插入订单表 _,err = tx.exec(“插入订单(id,customer_name,order_date)值(1,'john doe','2022-01-01')”)”)”) 如果err!= nil { 返回错误 } 返回无 }

我们无法期望重复每个功能的回滚/提交代码。我们在这里有两个选项创建一个类,该类将提供一个函数作为返回类型,在延期中执行时将提交/回滚TXN,或者创建一个包装类别,该类将将所有TXN func包含在一起并执行。

我选择了以后的选择,并且可以在下面看到代码的更改。


func testsqlwriteexec_createordertxn(t *testing.t){ db:= setupDatabase() //创建一个新的SQL Write Executor err:= dbutils.newsqltxnexec [orderRequest,orderProcessingResponse](context.todo(),db,nil和orderRequest {customername:“ customEra”:“ customId:productid:productid:1,tentity:10})。 statefulexec(insertorder)。 statefulexec(UpdateInventory)。 StateFulexec(插入)。 犯罪() //检查交易是否成功完成 如果err!= nil { T.Fatal(err) 返回 } 验证transactionsuccescessful(t,db) T.Cleanup( func(){ 清理(​​DB) db.close() },, ) }

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
}

此代码将非常精确和简洁。

如何实现核心逻辑

的想法是将TXN隔离到单个GO结构,以便它可以接受多个TXN。由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 ($1, $2, $3)", 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 - $1 WHERE id = $2 AND product_quantity >= $1", 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 ($1, 'Shipping Address')", order.CustomerName)
    if err != nil {
        return err
    }
    // Get the inserted Shipping ID
    orderProcessing.ShippingID, err = result.LastInsertId()
    return err
}

这两种是函数类型,将在TXN中处理某些内容。现在,在数据层中实现了这样的函数,并将其传递给执行程序类,该类负责注入args并执行函数。

struct { db *sql.db txn *sql.tx txnfns [] txnfn [t] statefultxnfns [] statefultxnfn [t,r] ProcessingReq *t 加工 *r CTX Context.Context 错误错误 }


这是我们存储所有txn_fn详细信息的地方,我们将拥有commit()方法来尝试进行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

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

// 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
}

让我知道是否有人希望在此基础上做出贡献并建立! 感谢您阅读这篇文章! https://in.linkedin.com/in/mahadev-k-934520223
https://x.com/mahadev_k_

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://dev.to/mahadev_k/building-robust-sql-transaction-execution-in-go-with-a-generic-framework-4j0f?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式接口中实现垂直滚动元素的CSS高度限制问题:考虑一个布局,其中我们具有与用户垂直滚动一起移动的可滚动地图div,同时与固定的固定sidebar保持一致。但是,地图的滚动无限期扩展,超过了视口的高度,阻止用户访问页面页脚。$("#map").css({ marginT...
    编程 发布于2025-03-25
  • ``STD :: LANEDER'如何解决工会中的const成员的编译器优化问题?
    ``STD :: LANEDER'如何解决工会中的const成员的编译器优化问题?
    Unveiling the Essence of Memory Laundering: A Deeper Dive into std::launderIn the realm of C standardization, P0137 introduces std::launder, a funct...
    编程 发布于2025-03-25
  • 如何处理PHP文件系统功能中的UTF-8文件名?
    如何处理PHP文件系统功能中的UTF-8文件名?
    在PHP的Filesystem functions中处理UTF-8 FileNames 在使用PHP的MKDIR函数中含有UTF-8字符的文件很多flusf-8字符时,您可能会在Windows Explorer中遇到comploreer grounder grounder grounder gro...
    编程 发布于2025-03-25
  • 在细胞编辑后,如何维护自定义的JTable细胞渲染?
    在细胞编辑后,如何维护自定义的JTable细胞渲染?
    在JTable中维护jtable单元格渲染后,在JTable中,在JTable中实现自定义单元格渲染和编辑功能可以增强用户体验。但是,至关重要的是要确保即使在编辑操作后也保留所需的格式。在设置用于格式化“价格”列的“价格”列,用户遇到的数字格式丢失的“价格”列的“价格”之后,问题在设置自定义单元格...
    编程 发布于2025-03-25
  • 如何使用PHP从XML文件中有效地检索属性值?
    如何使用PHP从XML文件中有效地检索属性值?
    从php $xml = simplexml_load_file($file); foreach ($xml->Var[0]->attributes() as $attributeName => $attributeValue) { echo $attributeName,...
    编程 发布于2025-03-25
  • 如何在Java中将字符串转换为字节数组(和后部)?
    如何在Java中将字符串转换为字节数组(和后部)?
    在使用Java中的字节数组时,将字符串转换为Java 通常是必要将字符串转换为字节数组的必要条件。可以使用几种方法来实现此转换。最简单的方法是getBytes(),它使用平台的默认字符编码将字符串转换为字节数组。例如: bytes = str.getBytes(); 但是,重要的是要注意,默认字符...
    编程 发布于2025-03-25
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-03-25
  • 如何在其容器中为DIV创建平滑的左右CSS动画?
    如何在其容器中为DIV创建平滑的左右CSS动画?
    通用CSS动画,用于左右运动 ,我们将探索创建一个通用的CSS动画,以向左和右移动DIV,从而到达其容器的边缘。该动画可以应用于具有绝对定位的任何div,无论其未知长度如何。问题:使用左直接导致瞬时消失 更加流畅的解决方案:混合转换和左 [并实现平稳的,线性的运动,我们介绍了线性的转换。这...
    编程 发布于2025-03-25
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-03-25
  • eval()vs. ast.literal_eval():对于用户输入,哪个Python函数更安全?
    eval()vs. ast.literal_eval():对于用户输入,哪个Python函数更安全?
    称量()和ast.literal_eval()中的Python Security 在使用用户输入时,必须优先确保安全性。强大的python功能eval()通常是作为潜在解决方案而出现的,但担心其潜在风险。 This article delves into the differences betwee...
    编程 发布于2025-03-25
  • 您可以使用CSS在Chrome和Firefox中染色控制台输出吗?
    您可以使用CSS在Chrome和Firefox中染色控制台输出吗?
    在javascript console 中显示颜色是可以使用chrome的控制台显示彩色文本,例如红色的redors,for for for for错误消息?回答是的,可以使用CSS将颜色添加到Chrome和Firefox中的控制台显示的消息(版本31或更高版本)中。要实现这一目标,请使用以下模...
    编程 发布于2025-03-25
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-03-25
  • 如何使用不同数量列的联合数据库表?
    如何使用不同数量列的联合数据库表?
    合并列数不同的表 当尝试合并列数不同的数据库表时,可能会遇到挑战。一种直接的方法是在列数较少的表中,为缺失的列追加空值。 例如,考虑两个表,表 A 和表 B,其中表 A 的列数多于表 B。为了合并这些表,同时处理表 B 中缺失的列,请按照以下步骤操作: 确定表 B 中缺失的列,并将它们添加到表的末...
    编程 发布于2025-03-25
  • 如何使用Regex在PHP中有效地提取括号内的文本
    如何使用Regex在PHP中有效地提取括号内的文本
    php:在括号内提取文本在处理括号内的文本时,找到最有效的解决方案是必不可少的。一种方法是利用PHP的字符串操作函数,如下所示: 作为替代 $ text ='忽略除此之外的一切(text)'; preg_match('#((。 &&& [Regex使用模式来搜索特...
    编程 发布于2025-03-25

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3