"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > 일반 프레임 워크와 함께 GO에서 강력한 SQL 트랜잭션 실행 구축

일반 프레임 워크와 함께 GO에서 강력한 SQL 트랜잭션 실행 구축

2025-03-23에 게시되었습니다
검색:669

Building Robust SQL Transaction Execution in Go with a Generic Framework

GO에서 SQL 데이터베이스와 작업 할 때 멀티 단계 트랜잭션 중에 원자력과 롤백 관리가 어려울 수 있습니다. 이 기사에서는 유연성을 위해 제네릭을 사용하여 GO에서 SQL 트랜잭션을 실행하기위한 강력하고 재사용 가능하며 테스트 가능한 프레임 워크를 작성하여 안내합니다.

트랜잭션 내에서 다중 종속 데이터베이스 작업을 실행하기위한 SQLWRITEEXEC 유틸리티를 구축하겠습니다. Instanteless 및 Stateful 운영을 모두 지원하여 관련 엔티티 삽입과 같은 정교한 워크 플로우를 가능하게하고 종속성을 원활하게 관리합니다.

SQL 트랜잭션을위한 프레임 워크가 필요한 이유는 무엇입니까?

실제 응용 프로그램에서 데이터베이스 작업은 거의 격리되지 않습니다. 이 시나리오를 고려하십시오 :

사용자를 삽입하고 원자 적으로 인벤토리를 업데이트합니다.

주문 생성 및 지불 처리, 일관성 보장.

여러 단계가 관련되면 데이터 무결성을 보장하기 위해 실패 중 롤백 관리가 중요합니다.

TXN 관리에서 GO와 협력합니다.

데이터베이스 TXN을 작성하는 경우 핵심 논리를 작성하기 전에 고려해야 할 몇 가지 보일러 플레이트가있을 수 있습니다. 이 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
}

func basictxn (db *sql.db) 오류 { // 트랜잭션을 시작합니다 tx, err : = db.begin () err! = nil {인 경우 반환 오류 } 연기 func () { r : = 복구 (); r! = nil { tx.rollback () } else if err! = nil { tx.rollback () } 또 다른 { tx.commit () } } () // 주문 테이블에 데이터를 삽입하십시오 _, err = tx.exec ( "주문 (id, customer_name, order_date) 값 (1, 'John Doe', '2022-01-01')")에 삽입 err! = nil {인 경우 반환 오류 } 반환 nil }

우리는 모든 함수에 대해 롤백/커밋 코드를 반복 할 것으로 기대할 수 없습니다. 여기에는 DEFER에서 실행될 때 TXN이 Commit/Rollback TXN을 커밋/롤백하거나 한 번에 모든 TXN Funcs를 함께 랩핑하고 한 번에 실행할 래퍼 클래스를 생성하는 반환 유형으로 기능을 제공하는 클래스를 작성하는 두 가지 옵션이 있습니다.

나는 나중에 선택을했고 코드의 변경은 아래에서 볼 수 있습니다.
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
}
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
}

func insertorder (ctx context.context, txn *sql.tx, Order *OrderRequest, OrderProcessing *OrderProcessingResponse) 오류 { // 순서 삽입 result, err : = tx.exec ( "주문 (customer_name, product_id, 수량) 값 ($ 1, $ 2, $ 3) 값에 삽입 err! = nil {인 경우 반환 오류 } // 삽입 된 주문 ID를 가져옵니다 OrderProcessing.OrderId, err = result.lastinsertid () 반환 오류 } func updateInventory (ctx context.context, txn *sql.tx, Order *OrderRequest, OrderProcessing *OrderProcessingResponse) 오류 { // 재고가 존재하고 수량이 존재하는지 확인하는 경우 수량이 더 큰 경우 업데이트 결과, err : = tx.exec ( "인벤토리 업데이트 세트 product_quantity = product_quantity - $ 1 여기서 id = $ 2 및 product_quantity> = $ 1", Order.Quantity, Order.ProductId) err! = nil {인 경우 반환 오류 } // 영향을받는 행 수를 얻습니다 RowsAffrected, err : = result.rowsaffrected () RowsAffed가있는 경우 == 0 { 리턴 오류 .New ( "불충분 한 재고") } 반환 오류 } func insertshipment (ctx context.context, txn *sql.tx, Order *OrderRequest, OrderProcessing *OrderProcessingResponse) 오류 { // 배송 삽입 result, err : = tx.exec ( "shippling_info (customer_name, shippling_address) 값 ($ 1, '배송 주소')", Order.CustomerName)에 삽입 err! = nil {인 경우 반환 오류 } // 삽입 된 배송 ID를 가져옵니다 OrderProcessing.shampingid, err = result.lastinsertid () 반환 오류 }

이 코드는 훨씬 더 정확하고 간결합니다.

핵심 논리가 구현되는 방법

아이디어는 TXN을 단일 GO 구조로 분리하여 여러 TXN을 수용 할 수있는 것입니다. TXN에 의해 ​​나는 우리가 클래스를 위해 만든 TXN으로 조치를 취할 기능을 의미합니다.
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
}

typt txnfn [t any] func (ctx context.context, txn *sql.tx, processingReq *t) 오류 statefultxnfn [t any, r] func (ctx context.context, txn *sql.tx, processingreq *t, processedres *r) 오류 유형

이 두 가지는 무언가를 처리하기 위해 TXN을 사용하는 기능 유형입니다. 이제 데이터 계층에서 이와 같은 함수를 구현하고이를 args 주입과 함수 실행을 처리하는 집행자 클래스로 전달합니다.
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
}

// SQL Write Executor는 쓰기 작업을 실행할 때 책임이 있습니다. // 종속 쓰기의 경우 ProcessReq에 종속 데이터를 추가하고 다음 함수 호출로 진행해야 할 수도 있습니다. sqltxnexec [t any, r] struct {유형 db *sql.db txn *sql.tx txnfns [] txnfn [t] statefultxnfns [] statefultxnfn [t, r] ProcessingReq *t ProcessedRes *r ctx context.context 오류 오류 }

이것은 우리가 모든 txn_fn 세부 사항을 저장하는 곳이며 TXN을 커밋하는 Commit () 메소드가 있습니다.
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
}

func (sqltxnexec [t, r]) commit () (err error) { 연기 func () { p : = revery (); p! = nil { s.txn.rollback () 공황 (P) } else if err! = nil { err = errors.join (err, s.txn.rollback ()) } 또 다른 { err = errors.join (err, s.txn.commit ()) } 반품 } () _, writefn : = 범위 s.txnfns { if err = writefn (s.ctx, s.txn, s.processingreq); err! = nil { 반품 } } for _, statefulwritefn : = 범위 s.statefultxnfns { err = statefulwritefn (s.ctx, s.txn, s.processingreq, s.processedres); err! = nil { 반품 } } 반품 }

Repo에서 더 많은 예와 테스트를 찾을 수 있습니다 -

https://github.com/mahadev-k/go-utils/tree/main/examples ]=&&]

요즘 분산 시스템과 합의 프로토콜에 편향되지만 여전히 SQL을 사용하고 여전히 존재합니다.


누군가 가이 위에 기여하고 구축하기를 원한다면 알려주세요 !!
지금까지 읽어 주셔서 감사합니다 !!
https://in.linkedin.com/in/mahadev-k-9345202222222223=&&] https://x.com/mahadev_k_=)

릴리스 선언문 이 기사는 다음과 같이 재현됩니다.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3