Bei der Arbeit mit SQL-Datenbanken in GO können die Gewährleistung von Atomizität und Verwaltung von Rollbacks während mehrstufiger Transaktionen eine Herausforderung sein. In diesem Artikel werde ich Sie durch das Erstellen eines robusten, wiederverwendbaren und überprüfbaren Frameworks zur Ausführung von SQL -Transaktionen in Go unter Verwendung von Generika für Flexibilität führen.
Wir erstellen ein SQLWriteExec -Dienstprogramm zur Ausführung mehrerer abhängiger Datenbankvorgänge in einer Transaktion. Es unterstützt sowohl staatenlose als auch staatliche Operationen und ermöglicht ausgefeilte Workflows wie Einfügen verwandter Entitäten, während Abhängigkeiten nahtlos verwaltet werden.
In realen Anwendungen sind Datenbankvorgänge selten isoliert. Betrachten Sie diese Szenarien:
Einfügen eines Benutzers und aktualisiert atomisch.
.
Erstellen einer Bestellung und Bearbeitung ihrer Zahlung, Gewährleistung der Konsistenz.
Wenn Sie eine Datenbank schreiben, gibt es möglicherweise mehrere Kesselplatten, die Sie möglicherweise berücksichtigen müssen, bevor Sie die Kernlogik schreiben. Während dieses TXN -Management von Spring Boot in Java verwaltet wird und Sie sich beim Schreiben von Code in Java nie viel darum gekümmert haben, ist dies in Golang nicht der Fall. Ein einfaches Beispiel wird unten angegeben
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) Fehler { // eine Transaktion starten tx, err: = db.begin () Wenn er! = nil { Return err zurück } Defer func () { wenn r: = recover (); r! = nil { tx.rollback () } else wenn err! = nil { tx.rollback () } anders { tx.commit () } } ()) // Daten in die Bestellentabelle einfügen _, err = tx.exec ("In Bestellungen einfügen (ID, Customer_Name, Order_date) Werte (1, 'John Doe', '2022-01-01')"))) Wenn er! = nil { Return err zurück } Null zurückkehren }
Wir können nicht erwarten, den Rollback/Commit -Code für jede Funktion zu wiederholen. Wir haben hier zwei Optionen, die entweder eine Klasse erstellen, die eine Funktion als Rückgabetyp liefert, die bei der Ausführung im Auflauf TXN verpflichtet/rollback txn erstellt oder eine Wrapper -Klasse erstellt, die alle TXN -Funktionen zusammenwickelt und in einem Go.
.
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 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 testsqlwriteExec_createordertxn (t *testing.t) { DB: = setupdatabase () // Erstellen Sie einen neuen SQL -Writing Executor err: = dbutils.newsqltxnexec [orderRequest, orderprocessingResponse] (context.todo (), db, nil & orderRequest {Customername: "Customera", ProductID: 1, Menge: 10}). Statefulexec (InsertOrder). StateFullexec (UpdateInventory). StateFulexec (Einfügung). Begehen() // Überprüfen Sie, ob die Transaktion erfolgreich begangen wurde Wenn er! = nil { t.fatal (arr) zurückkehren } verifyTransactionscessful (t, db) t.cleanup ( func () { Reinigung (DB) db.close () }, ) }
func InsertOrder (ctx context.context, txn * // Bestellung einfügen Ergebnis, err: = txn.exec ("In Bestellungen einfügen (Customer_Name, product_id, Menge) Werte ($ 1, $ 2, $ 3)", order.customername, order.productid, order.quantity) Wenn er! = nil { Return err zurück } // Die eingefügte Bestell -ID erhalten orderprocessing.orderId, err = result.lastinSertId () Return err zurück } func updateInventory (ctx context.context, txn * // Inventar aktualisieren, wenn es vorhanden ist und die Menge größer ist als die Menge überprüfen, ob es vorhanden ist Ergebnis, err: = txn.exec ("Inventar set product_quantity = product_quantity - $ 1 wob Wenn er! = nil { Return err zurück } // Die Anzahl der betroffenen Zeilen erhalten RowsAffected, err: = result.rowsafflected () Wenn rowsaffcted == 0 { Rückgabefehler.New ("Unzureichend Inventar") } Return err zurück } Func Insertship (CTX Context.Context, Txn * // Sendung einfügen Ergebnis, err: = txn.exec ("In Shipping_Info einfügen (Customer_Name, Shipping_address) Werte ($ 1, 'Versandadresse')", order.customername) Wenn er! = nil { Return err zurück } // Holen Sie sich die eingeführte Versand -ID orderprocessing.shippingid, err = result.lastinSertId () Return err zurück }
Wie die Kernlogik implementiert wird
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
type txnfn [t irgendein] func (ctx context.context, txn *sql.tx, procesalreq *t) Fehler
Typ StateFullTxnfn [t beliebig, r jeder] func (ctx context.context, 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 }
// sql write Executor ist bei der Ausführung von Schreibvorgängen verantwortlich
// Für abhängige Schreibvorgänge müssen Sie möglicherweise die abhängigen Daten zu procesReq hinzufügen und mit dem nächsten Funktionsaufruf fortfahren
Geben Sie SQLTXNexec [t an, r jeder] Struktur {
db *sql.db
txn *sql.tx
txnfns [] txnfn [t]
statefultxnfns [] statefulTxnfn [t, r]
procesreq *t
Processedres *r
ctx context.context
Fehler Fehler
}
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 }
func (s *
Defer func () {
wenn p: = recover (); p! = nil {
S.Txn.Rollback ()
Panik (p)
} else wenn err! = nil {
err = errors.join (err, S.Txn.rollback ())
} anders {
err = errors.join (err, S.Txn.Commit ())
}
zurückkehren
} ())
Für _, writeFn: = Bereich S.Txnfns {
if err = writefn (S.CTX, S.TXN, S.PROCESSINGREQ); err! = nil {
zurückkehren
}
}
für _, statefulwriteFn: = Bereich s.StatefulTxnfns {
if err = statefulwriteFn (S.Ctx, S.TXN, S.Processingreq, S. Processedres); err! = nil {
zurückkehren
}
}
zurückkehren
}
Sie können weitere Beispiele und Tests im Repo finden -
https://github.com/mahadev-k/go-utils/tree/main/examples?&&&] Obwohl wir heutzutage zu verteilten Systemen und Konsensprotokoll voreinstimmen, verwenden wir immer noch SQL und es existiert immer noch.
Lassen Sie mich wissen, ob jemand dazu beitragen und darüber aufbauen möchte !!
Danke, dass du so weit gelesen hast !!
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3