Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Pertanyaan yang merangkumi Sqlc untuk melaksanakan operasi transaksi yang lebih mudah

Pertanyaan yang merangkumi Sqlc untuk melaksanakan operasi transaksi yang lebih mudah

王林
王林asal
2024-08-05 18:39:20784semak imbas

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

Apakah itu SQLC

SQLC ialah alat pembangunan yang berkuasa yang fungsi terasnya adalah untuk menukar pertanyaan SQL kepada kod Go selamat jenis. Dengan menghuraikan pernyataan SQL dan menganalisis struktur pangkalan data, sqlc secara automatik boleh menjana struktur dan fungsi Go yang sepadan, dengan sangat memudahkan proses penulisan kod untuk operasi pangkalan data.

Menggunakan sqlc, pembangun boleh menumpukan pada menulis pertanyaan SQL dan membiarkan penjanaan kod Go yang membosankan berfungsi kepada alat, dengan itu mempercepatkan proses pembangunan dan meningkatkan kualiti kod.

Pelaksanaan transaksi SQLC

Kod yang dijana oleh Sqlc biasanya mengandungi struktur Pertanyaan, yang merangkumi semua operasi pangkalan data. Struktur ini melaksanakan antara muka Querier umum, yang mentakrifkan semua kaedah pertanyaan pangkalan data.

Kuncinya ialah fungsi Baharu yang dijana oleh sqlc boleh menerima sebarang objek yang melaksanakan antara muka DBTX, termasuk *sql.DB dan *sql.Tx.

Inti pelaksanaan transaksi adalah untuk menggunakan polimorfisme antara muka Go. Apabila anda perlu melakukan operasi dalam urus niaga, anda mencipta objek *sql.Tx dan kemudian menyerahkannya kepada fungsi Baharu untuk mencipta contoh Pertanyaan baharu. Kejadian ini akan melaksanakan semua operasi dalam konteks transaksi.

Andaikan kami menyambung ke pangkalan data Postgres melalui pgx dan memulakan Pertanyaan dengan kod berikut.

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

Enkapsulasi urus niaga

Kod berikut ialah enkapsulasi transaksi sqlc yang bijak, yang memudahkan proses menggunakan transaksi pangkalan data dalam Go. Fungsi ini menerima konteks dan fungsi panggil balik sebagai parameter Fungsi panggil balik ini ialah operasi khusus yang ingin dilakukan oleh pengguna dalam transaksi.

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

Fungsi ini mula-mula memulakan urus niaga baharu dan kemudian menangguhkan pelaksanaan untuk memastikan urus niaga itu akhirnya akan ditarik balik melainkan ia dilakukan secara eksplisit. Ini adalah mekanisme keselamatan untuk mengelakkan transaksi yang belum selesai daripada menduduki sumber. Seterusnya, fungsi memanggil panggilan balik yang dibekalkan pengguna, menghantar objek pertanyaan dengan konteks transaksi, membolehkan pengguna melaksanakan operasi pangkalan data yang diperlukan dalam transaksi.

Jika panggilan balik berjaya dilaksanakan tanpa ralat, fungsi melakukan transaksi. Sebarang ralat yang berlaku semasa proses akan menyebabkan transaksi ditarik balik. Kaedah ini bukan sahaja memastikan ketekalan data, tetapi juga sangat memudahkan pengendalian ralat.

Keanggunan enkapsulasi ini ialah ia menyembunyikan logik pengurusan transaksi yang kompleks di sebalik panggilan fungsi yang ringkas. Pengguna boleh menumpukan pada menulis logik perniagaan tanpa perlu risau tentang memulakan, melakukan atau membatalkan urus niaga.

Penggunaan kod ini agak intuitif. Anda boleh memanggil fungsi db.WithTransaction di mana anda perlu melakukan transaksi, menghantar fungsi sebagai parameter yang mentakrifkan semua operasi pangkalan data yang anda ingin lakukan dalam transaksi.

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

Dalam contoh ini, kami mencipta pengguna dan siaran dalam transaksi. Jika mana-mana operasi gagal, keseluruhan transaksi akan ditarik balik. Jika semua operasi berjaya, transaksi dilakukan.

Faedah pendekatan ini ialah anda tidak perlu mengurus secara manual permulaan, komit atau rollback transaksi, semua ini dikendalikan oleh fungsi db.WithTransaction. Anda hanya perlu menumpukan pada operasi pangkalan data sebenar yang dilakukan dalam transaksi. Ini sangat memudahkan kod dan mengurangkan kemungkinan ralat.

Pembungkusan lanjut

Kaedah pembungkusan yang dinyatakan di atas bukan tanpa kekurangannya.

Pengenkapsulan transaksi mudah ini mempunyai had apabila berurusan dengan transaksi bersarang. Ini kerana ia mencipta transaksi baharu setiap kali dan bukannya menyemak sama ada anda sudah berada dalam satu transaksi.

Untuk melaksanakan pemprosesan transaksi bersarang, kita mesti mendapatkan objek transaksi semasa, tetapi objek transaksi semasa tersembunyi di dalam sqlc.Queries, jadi kita mesti melanjutkan sqlc.Queries.

Struktur yang memanjangkan sqlc.Queries dicipta oleh kami sebagai Repositori Ia memanjangkan *sqlc.Queries dan menambah kumpulan atribut baharu, yang merupakan penunjuk jenis pgxpool.Pool.

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

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

Tetapi apabila kami mula menulis kod, kami akan mendapati bahawa *pgxpool.Pool tidak memenuhi antara muka pgx.Tx Ini kerana *pgxpool.Pool tidak mempunyai kaedah Rollback dan Commit. Ia hanya mengandungi Mula untuk memulakan transaksi. Kaedah, untuk menyelesaikan masalah ini, kami terus meluaskan Repositori, menambah atribut tx baharu padanya dan menambah kaedah NewRepositoriesTx baharu padanya.

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

Sekarang, terdapat kedua-dua atribut pool dan tx dalam struktur Repositori kami. Ini mungkin tidak kelihatan sangat elegan Mengapa kita tidak boleh mengabstrak jenis TX bersatu Terdapat hanya kaedah untuk memulakan transaksi, tetapi bukan kaedah untuk menamatkan transaksi Satu cara untuk menyelesaikan masalah ini ialah dengan mencipta struktur RepositoriesTX lain dan menyimpan pgx.Tx di dalamnya dan bukannya *pgxpool.Pool, tetapi ini mungkin menyebabkan There. adalah soalan baharu. Salah satu daripadanya ialah kita mungkin perlu melaksanakan kaedah WithTransaction untuk kedua-duanya.

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

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

Atas ialah kandungan terperinci Pertanyaan yang merangkumi Sqlc untuk melaksanakan operasi transaksi yang lebih mudah. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel sebelumnya:Laksanakan Cache LRU dalam GoArtikel seterusnya:Laksanakan Cache LRU dalam Go