«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Создание надежного выполнения транзакции SQL в Go с общей структурой

Создание надежного выполнения транзакции SQL в Go с общей структурой

Опубликовано в 2025-03-23
Просматривать:806

Building Robust SQL Transaction Execution in Go with a Generic Framework

при работе с базами данных SQL в GO, обеспечение атома и управления откатами во время многоэтапных транзакций может быть сложной задачей. В этой статье я направляю вас через создание надежной, многоразовой и тестируемой структуры для выполнения транзакций SQL в Go, используя Generics для гибкости.

]

мы создадим утилиту SQLWriteExec для выполнения нескольких зависимых операций базы данных в рамках транзакции. Он поддерживает как без сохранения состояния, так и государственные операции, обеспечивая сложные рабочие процессы, такие как вставка связанных объектов при плавном управлении зависимостями.

]

] Зачем нам нужна структура для транзакций SQL?

]

в реальных приложениях операции базы данных редко изолированы. Рассмотрим эти сценарии:

]

вставка пользователя и обновлять их инвентарь атомно.
] Создание заказа и обработка своего платежа, обеспечивая согласованность.
] С учетом нескольких шагов управление откатами во время сбоев становится решающим для обеспечения целостности данных.

]

] Работа с Go In Texn Management.

]

Если вы пишете базу данных txn, может быть несколько котловых пластин, которые вам может потребоваться рассмотреть перед написанием основной логики. В то время как это управление TXN управляется Spring Boot в Java, и вы никогда не беспокоились о них во время написания кода на Java, но это не так в Голанге. Простой пример приведен ниже
]

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

мы не можем рассчитывать на повторить код Rollback/Commit для каждой функции. У нас есть два варианта здесь либо создать класс, который предоставит функцию в качестве типа возврата, который при выполнении в 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 ($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 на одну структуру 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 для чего -то обработать. Теперь в уровне данных внедряет функцию создать такую ​​функцию и передайте ее классу исполнителя, который заботится о инъекции ARG и выполнении функции.
]

]
// 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_ts&&&&]
]
]

Заявление о выпуске Эта статья воспроизводится по адресу: https://dev.to/mahadev_k/building-robust-sql-transaction-excution-in-go-with-a-generic-framework-4j0f?1 Если есть какое-либо нарушение, пожалуйста, свяжитесь с [email protected], чтобы удалить его.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3