Heim > Artikel > Backend-Entwicklung > Abfragen, die Sqlc kapseln, um bequemere Transaktionsvorgänge zu implementieren
SQLC ist ein leistungsstarkes Entwicklungstool, dessen Kernfunktion darin besteht, SQL-Abfragen in typsicheren Go-Code umzuwandeln. Durch das Parsen von SQL-Anweisungen und das Analysieren von Datenbankstrukturen kann SQLC automatisch entsprechende Go-Strukturen und -Funktionen generieren, wodurch der Code-Schreibprozess für Datenbankoperationen erheblich vereinfacht wird.
Mit sqlc können sich Entwickler auf das Schreiben von SQL-Abfragen konzentrieren und die mühsame Arbeit der Go-Codegenerierung dem Tool überlassen, wodurch der Entwicklungsprozess beschleunigt und die Codequalität verbessert wird.
Der von Sqlc generierte Code enthält normalerweise eine Abfragestruktur, die alle Datenbankoperationen kapselt. Diese Struktur implementiert eine allgemeine Querier-Schnittstelle, die alle Datenbankabfragemethoden definiert.
Der Schlüssel ist, dass die von sqlc generierte neue Funktion jedes Objekt akzeptieren kann, das die DBTX-Schnittstelle implementiert, einschließlich *sql.DB und *sql.Tx.
Der Kern der Transaktionsimplementierung besteht in der Nutzung des Schnittstellenpolymorphismus von Go. Wenn Sie Vorgänge innerhalb einer Transaktion ausführen müssen, erstellen Sie ein *sql.Tx-Objekt und übergeben es dann an die New-Funktion, um eine neue Abfrageinstanz zu erstellen. Diese Instanz führt alle Vorgänge im Kontext einer Transaktion aus.
Angenommen, wir stellen über pgx eine Verbindung zur Postgres-Datenbank her und initialisieren Abfragen mit dem folgenden Code.
var Pool *pgxpool.Pool var Queries *sqlc.Queries func init() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() connConfig, err := pgxpool.ParseConfig("postgres://user:password@127.0.0.1:5432/db?sslmode=disable") if err != nil { panic(err) } pool, err := pgxpool.NewWithConfig(ctx, connConfig) if err != nil { panic(err) } if err := pool.Ping(ctx); err != nil { panic(err) } Pool = pool Queries = sqlc.New(pool) }
Der folgende Code ist eine clevere SQLC-Transaktionskapselung, die den Prozess der Verwendung von Datenbanktransaktionen in Go vereinfacht. Die Funktion akzeptiert einen Kontext und eine Rückruffunktion als Parameter. Diese Rückruffunktion ist die spezifische Operation, die der Benutzer in der Transaktion ausführen möchte.
func WithTransaction(ctx context.Context, callback func(qtx *sqlc.Queries) (err error)) (err error) { tx, err := Pool.Begin(ctx) if err != nil { return err } defer func() { if e := tx.Rollback(ctx); e != nil && !errors.Is(e, pgx.ErrTxClosed) { err = e } }() if err := callback(Queries.WithTx(tx)); err != nil { return err } return tx.Commit(ctx) }
Die Funktion startet zunächst eine neue Transaktion und verzögert dann die Ausführung, um sicherzustellen, dass die Transaktion schließlich zurückgesetzt wird, sofern sie nicht explizit festgeschrieben wird. Dies ist ein Sicherheitsmechanismus, um zu verhindern, dass nicht abgeschlossene Transaktionen Ressourcen belegen. Als Nächstes ruft die Funktion den vom Benutzer bereitgestellten Rückruf auf und übergibt ein Abfrageobjekt mit einem Transaktionskontext, sodass der Benutzer die erforderlichen Datenbankoperationen innerhalb der Transaktion ausführen kann.
Wenn der Rückruf erfolgreich und ohne Fehler ausgeführt wird, schreibt die Funktion die Transaktion fest. Alle während des Vorgangs auftretenden Fehler führen dazu, dass die Transaktion zurückgesetzt wird. Diese Methode stellt nicht nur die Datenkonsistenz sicher, sondern vereinfacht auch die Fehlerbehandlung erheblich.
Die Eleganz dieser Kapselung besteht darin, dass sie komplexe Transaktionsverwaltungslogik hinter einem einfachen Funktionsaufruf verbirgt. Benutzer können sich auf das Schreiben von Geschäftslogik konzentrieren, ohne sich Gedanken über das Starten, Festschreiben oder Zurücksetzen von Transaktionen machen zu müssen.
Die Verwendung dieses Codes ist recht intuitiv. Sie können die Funktion db.WithTransaction dort aufrufen, wo Sie eine Transaktion ausführen müssen, indem Sie eine Funktion als Parameter übergeben, der alle Datenbankoperationen definiert, die Sie innerhalb der Transaktion ausführen möchten.
err := db.WithTransaction(ctx, func(qtx *sqlc.Queries) error { // 在这里执行你的数据库操作 // 例如: _, err := qtx.CreateUser(ctx, sqlc.CreateUserParams{ Name: "Alice", Email: "alice@example.com", }) if err != nil { return err } _, err = qtx.CreatePost(ctx, sqlc.CreatePostParams{ Title: "First Post", Content: "Hello, World!", AuthorID: newUserID, }) if err != nil { return err } // 如果所有操作都成功,返回 nil return nil }) if err != nil { // 处理错误 log.Printf("transaction failed: %v", err) } else { log.Println("transaction completed successfully") }
In diesem Beispiel erstellen wir einen Benutzer und einen Beitrag in einer Transaktion. Wenn ein Vorgang fehlschlägt, wird die gesamte Transaktion zurückgesetzt. Wenn alle Vorgänge erfolgreich sind, wird die Transaktion festgeschrieben.
Der Vorteil dieses Ansatzes besteht darin, dass Sie den Start, das Commit oder das Rollback der Transaktion nicht manuell verwalten müssen. All dies wird von der Funktion db.WithTransaction erledigt. Sie müssen sich nur auf die tatsächlichen Datenbankoperationen konzentrieren, die innerhalb der Transaktion ausgeführt werden. Dadurch wird der Code erheblich vereinfacht und die Möglichkeit von Fehlern verringert.
Die oben erwähnte Verpackungsmethode ist nicht ohne Mängel.
Diese einfache Transaktionskapselung weist Einschränkungen beim Umgang mit verschachtelten Transaktionen auf. Dies liegt daran, dass jedes Mal eine neue Transaktion erstellt wird, anstatt zu prüfen, ob Sie sich bereits in einer befinden.
Um die verschachtelte Transaktionsverarbeitung zu implementieren, müssen wir das aktuelle Transaktionsobjekt abrufen, aber das aktuelle Transaktionsobjekt ist in sqlc.Queries verborgen, daher müssen wir sqlc.Queries erweitern.
Die Struktur, die sqlc.Queries erweitert, wird von uns als Repositories erstellt. Sie erweitert *sqlc.Queries und fügt einen neuen Attributpool hinzu, der ein Zeiger vom Typ pgxpool.Pool ist.
type Repositories struct { *sqlc.Queries pool *pgxpool.Pool } func NewRepositories(pool *pgxpool.Pool) *Repositories { return &Repositories{ pool: pool, Queries: sqlc.New(pool), } }
Aber wenn wir mit dem Schreiben von Code beginnen, werden wir feststellen, dass *pgxpool.Pool die pgx.Tx-Schnittstelle nicht erfüllt. Dies liegt daran, dass *pgxpool.Pool die Methoden Rollback und Commit fehlen. Es enthält nur Begin zum Starten einer Transaktion. Methode: Um dieses Problem zu lösen, erweitern wir Repositories weiter, fügen ihr ein neues Attribut tx und eine neue NewRepositoriesTx-Methode hinzu.
type Repositories struct { *sqlc.Queries tx pgx.Tx pool *pgxpool.Pool } func NewRepositoriesTx(tx pgx.Tx) *Repositories { return &Repositories{ tx: tx, Queries: sqlc.New(tx), } }
Nun gibt es in unserer Repository-Struktur sowohl Pool- als auch TX-Attribute. Das sieht vielleicht nicht sehr elegant aus. Warum können wir nicht einen einheitlichen TX-Typ abstrahieren, nämlich *pgxpool.Pool? Es gibt nur eine Methode zum Starten einer Transaktion, aber keine Methode zum Beenden der Transaktion. Eine Möglichkeit, dieses Problem zu lösen, besteht darin, eine andere RepositoriesTX-Struktur zu erstellen und darin pgx.Tx anstelle von *pgxpool.Pool zu speichern. Dies kann jedoch zu There führen Eine davon ist, dass wir möglicherweise die WithTransaction-Methode für beide implementieren müssen. Über die andere Frage werden wir nun zuerst die WithTransaction-Methode von Repositories implementieren.
func (r *Repositories) WithTransaction(ctx context.Context, fn func(qtx *Repositories) (err error)) (err error) { var tx pgx.Tx if r.tx != nil { tx, err = r.tx.Begin(ctx) } else { tx, err = r.pool.Begin(ctx) } if err != nil { return err } defer func() { if e := tx.Rollback(ctx); e != nil && !errors.Is(e, pgx.ErrTxClosed) { err = e } }() if err := fn(NewRepositoriesTx(tx)); err != nil { return err } return tx.Commit(ctx) }
这个方法和上一章节实现的 WithTransaction 主要不同是,他是实现在 *Repositories 上面而不是全局的,这样我们就可以通过 (r *Repositories) 中的 pgx.Tx 来开始嵌套事务了。
在没有开始事务的时候,我们可以调用 repositories.WithTransaction 来开启一个新的事务。
err := db.repositories.WithTransaction(ctx, func(tx *db.Repositories) error { return nil })
多级事务也是没有问题的,非常容易实现。
err := db.repositories.WithTransaction(ctx, func(tx *db.Repositories) error { // 假设此处进行了一些数据操作 // 然后,开启一个嵌套事务 return tx.WithTransaction(ctx, func(tx *db.Repositories) error { // 这里可以在嵌套事务中进行一些操作 return nil }) })
这个封装方案有效地确保了操作的原子性,即使其中任何一个操作失败,整个事务也会被回滚,从而保障了数据的一致性。
本文介绍了一个使用 Go 和 pgx 库封装 SQLC 数据库事务的方案。
核心是 Repositories 结构体,它封装了 SQLC 查询接口和事务处理逻辑。通过 WithTransaction 方法,我们可以在现有事务上开始新的子事务或在连接池中开始新的事务,并确保在函数返回时回滚事务。
构造函数 NewRepositories 和 NewRepositoriesTx 分别用于创建普通和事务内的 Repositories 实例。
这样可以将多个数据库操作封装在一个事务中,如果任何一个操作失败,事务将被回滚,提高了代码的可维护性和可读性。
Das obige ist der detaillierte Inhalt vonAbfragen, die Sqlc kapseln, um bequemere Transaktionsvorgänge zu implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!