搜尋
首頁後端開發Golang封裝 Sqlc 的 Queries 實作更方便的事務操作

封装 Sqlc 的 Queries 实现更方便的事务操作

SQLC 是什麼

SQLC 是一個強大的開發工具,它的核心功能是將SQL查詢轉換成型別安全的Go程式碼。透過解析SQL語句和分析資料庫結構,sqlc能夠自動產生對應的Go結構體和函數,大大簡化了資料庫操作的程式碼編寫過程。

使用sqlc,開發者可以專注於編寫SQL查詢,而將繁瑣的Go程式碼產生工作交給工具完成,從而加速開發過程並提高程式碼品質。

SQLC 的交易實現

Sqlc 產生的程式碼通常包含一個Queries結構體,它封裝了所有資料庫操作。這個結構體實作了一個通用的Querier接口,該接口定義了所有資料庫查詢方法。

關鍵在於,sqlc產生的New函數可以接受任何實作了DBTX介面的對象,包括*sql.DB和*sql.Tx。

事務實現的核心在於利用Go的介面多態性。當你需要在事務中執行操作時,可以建立一個*sql.Tx對象,然後將其傳遞給New函數來建立一個新的Queries實例。這個實例會在交易的上下文中執行所有操作。

假設我們透過 pgx 連接 Postgres 資料庫,並以下程式碼初始化 Queries。

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

對事務的封裝

下面這段程式碼是個巧妙的sqlc交易封裝,它簡化了在Go中使用資料庫事務的過程。函數接受一個上下文和一個回呼函數作為參數,這個回呼函數就是使用者想在交易中執行的具體操作。

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

函數首先開始一個新的事務,然後透過延遲執行來確保事務最終會被回滾,除非它被明確提交。這是一個安全機制,防止未完成的事務佔用資源。接著,函數呼叫使用者提供的回調,傳入一個帶有事務上下文的查詢對象,允許使用者在事務中執行所需的資料庫操作。

如果回呼成功執行且沒有錯誤,函數會提交交易。任何在過程中出現的錯誤都會導致交易回滾。這種方法既保證了資料一致性,也大大簡化了錯誤處理。

這個封裝的優雅之處在於,它將複雜的事務管理邏輯隱藏在一個簡單的函式呼叫之後。使用者可以專注於編寫業務邏輯,而不必擔心交易的開始、提交或回滾。

這段程式碼的使用方法相當直觀。你可以在需要執行事務的地方呼叫 db.WithTransaction 函數,並傳入一個函數作為參數,該函數定義了你想在事務中執行的所有資料庫操作。

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")
}

在這個例子中,我們在事務中建立了一個使用者和一個貼文。如果任何操作失敗,整個交易都會回滾。如果所有操作都成功,事務會被提交。

這種方法的好處是你不需要手動管理事務的開始、提交或回滾,所有這些都由 db.WithTransaction 函數處理。你只需要專注於在事務中執行的實際資料庫操作。這大大簡化了程式碼,並減少了出錯的可能性。

更進一步的封裝

上述的這種封裝方式並非毫無缺點。

這種簡單的事務封裝在處理巢狀事務時有其限制。這是因為它每次都會創建一個新的事務,而不是檢查是否已經在一個事務中。

為了實現巢狀事務處理,我們必須可以獲得當前事務對象,但是當前事務對像是隱藏在 sqlc.Queries 內部的,所以必須我們需要擴展 sqlc.Queries。

擴展 sqlc.Queries 的結構體被我們創建為 Repositories,他擴展了 *sqlc.Queries 並添加了一個新的屬性 pool,這是一個 pgxpool.Pool 類型的指標。

type Repositories struct {
    *sqlc.Queries
    pool *pgxpool.Pool
}

func NewRepositories(pool *pgxpool.Pool) *Repositories {
    return &Repositories{
        pool:    pool,
        Queries: sqlc.New(pool),
    }
}

但是當我們開始編寫程式碼的時候就會發現,*pgxpool.Pool 並不能滿足pgx.Tx 接口,這是因為*pgxpool.Pool 中缺少Rollback 和Commit 方法,他只包含用於開始事務的Begin方法,為了解決這個問題,我們繼續擴展Repositories 在其中新增一個新的屬性tx,並為其添加新的NewRepositoriesTx 方法。

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),
    }
}

現在,我們的Repositories 結構體中同時存在pool 和tx 屬性,這可能看起來不是很優雅,為什麼不能抽像出來一個統一的TX 類型呢,其實還是上面說到的原因,即*pgxpool.Pool只有開始事務的方法,而沒有結束事務的方法,而解決這個問題的方法之一是,再創建一個RepositoriesTX 結構體,在其中存儲pgx.Tx 而不是*pgxpool.Pool ,但是這樣做可能又會帶來新的問題,其中之一是,我們可能要為他們兩者分別實作WithTransaction 方法,至於另外一個問題,我們後面在說,現在讓我們先來實作Repositories 的WithTransaction 方法。

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 实例。

这样可以将多个数据库操作封装在一个事务中,如果任何一个操作失败,事务将被回滚,提高了代码的可维护性和可读性。

以上是封裝 Sqlc 的 Queries 實作更方便的事務操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Go語言包導入:帶下劃線和不帶下劃線的區別是什麼?Go語言包導入:帶下劃線和不帶下劃線的區別是什麼?Mar 03, 2025 pm 05:17 PM

本文解釋了GO的軟件包導入機制:命名imports(例如導入“ fmt”)和空白導入(例如導入_ fmt; fmt;)。 命名導入使包裝內容可訪問,而空白導入僅執行t

Go語言中如何將MySQL查詢結果List轉換為自定義結構體切片?Go語言中如何將MySQL查詢結果List轉換為自定義結構體切片?Mar 03, 2025 pm 05:18 PM

本文詳細介紹了MySQL查詢結果的有效轉換為GO結構切片。 它強調使用數據庫/SQL的掃描方法來最佳性能,避免手動解析。 使用DB標籤和Robus的結構現場映射的最佳實踐

Beego框架中NewFlash()函數如何實現頁面間短暫信息傳遞?Beego框架中NewFlash()函數如何實現頁面間短暫信息傳遞?Mar 03, 2025 pm 05:22 PM

本文解釋了Beego的NewFlash()函數,用於Web應用程序中的頁間數據傳輸。 它專注於使用newflash()在控制器之間顯示臨時消息(成功,錯誤,警告),並利用會話機制。 Lima

如何編寫模擬對象和存根以進行測試?如何編寫模擬對象和存根以進行測試?Mar 10, 2025 pm 05:38 PM

本文演示了創建模擬和存根進行單元測試。 它強調使用接口,提供模擬實現的示例,並討論最佳實踐,例如保持模擬集中並使用斷言庫。 文章

如何定義GO中仿製藥的自定義類型約束?如何定義GO中仿製藥的自定義類型約束?Mar 10, 2025 pm 03:20 PM

本文探討了GO的仿製藥自定義類型約束。 它詳細介紹了界面如何定義通用功能的最低類型要求,從而改善了類型的安全性和代碼可重複使用性。 本文還討論了局限性和最佳實踐

Go語言如何便捷地寫入文件?Go語言如何便捷地寫入文件?Mar 03, 2025 pm 05:15 PM

本文詳細介紹了在GO中詳細介紹有效的文件,將OS.WriteFile(適用於小文件)與OS.openfile和緩衝寫入(最佳大型文件)進行比較。 它強調了使用延遲並檢查特定錯誤的可靠錯誤處理。

您如何在GO中編寫單元測試?您如何在GO中編寫單元測試?Mar 21, 2025 pm 06:34 PM

本文討論了GO中的編寫單元測試,涵蓋了最佳實踐,模擬技術和有效測試管理的工具。

如何使用跟踪工具了解GO應用程序的執行流?如何使用跟踪工具了解GO應用程序的執行流?Mar 10, 2025 pm 05:36 PM

本文使用跟踪工具探討了GO應用程序執行流。 它討論了手冊和自動儀器技術,比較諸如Jaeger,Zipkin和Opentelemetry之類的工具,並突出顯示有效的數據可視化

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
2 週前By尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
4 週前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器