1. Pengenalan Transaksi dan Konsistensi Data
Sebuah transaksi merupakan unit logis dalam proses eksekusi dari sistem manajemen basis data, terdiri dari serangkaian operasi. Operasi-operasi ini entah berhasil semua atau gagal semua, dan dianggap sebagai keseluruhan yang tidak dapat dipisahkan. Karakteristik kunci dari transaksi dapat disimpulkan dengan ACID:
- Atomicity: Semua operasi dalam transaksi entah diselesaikan sepenuhnya atau tidak diselesaikan sama sekali; penyelesaian parsial tidak mungkin.
- Consistency: Sebuah transaksi harus memindahkan basis data dari satu keadaan konsisten ke keadaan konsisten lainnya.
- Isolation: Eksekusi dari transaksi harus terisolasi dari gangguan oleh transaksi lain, dan data antara transaksi konkuren harus diisolasi.
- Durability: Setelah sebuah transaksi dikonfirmasi, modifikasi yang dilakukannya akan bertahan dalam basis data.
Konsistensi data mengacu pada pemeliharaan keadaan data yang benar dan valid dalam basis data setelah serangkaian operasi. Dalam skenario-skenario akses konkuren atau kegagalan sistem, konsistensi data sangat penting, dan transaksi menyediakan mekanisme untuk memastikan bahwa konsistensi data tidak dikorbankan, bahkan saat terjadi kesalahan atau konflik.
2. Tinjauan dari Kerangka Kerja ent
ent
adalah sebuah kerangka kerja entitas yang, melalui pembangkitan kode dalam bahasa pemrograman Go, menyediakan API yang aman tipe untuk mengoperasikan basis data. Hal ini membuat operasi basis data lebih intuitif dan aman, mampu menghindari isu-isu keamanan seperti injeksi SQL. Dalam hal pemrosesan transaksi, kerangka kerja ent
menyediakan dukungan yang kuat, memungkinkan pengembang untuk menjalankan operasi transaksi yang kompleks dengan kode yang ringkas dan memastikan bahwa properti ACID dari transaksi dipenuhi.
3. Memulai Transaksi
3.1 Cara Memulai Transaksi dalam ent
Dalam kerangka kerja ent
, transaksi baru dapat dengan mudah dimulai dalam konteks tertentu menggunakan metode client.Tx
, mengembalikan objek transaksi Tx
. Contoh kode adalah sebagai berikut:
tx, err := client.Tx(ctx)
if err != nil {
// Tangani kesalahan saat memulai transaksi
return fmt.Errorf("Kesalahan terjadi saat memulai transaksi: %w", err)
}
// Jalankan operasi-operasi selanjutnya menggunakan tx...
3.2 Melakukan Operasi dalam Sebuah Transaksi
Setelah objek Tx
berhasil dibuat, dapat digunakan untuk menjalankan operasi-operasi basis data. Semua operasi membuat, menghapus, memperbarui, dan mengonfirmasikan operasi-operasi yang dieksekusi pada objek Tx
akan menjadi bagian dari transaksi. Contoh berikut menunjukkan serangkaian operasi:
hub, err := tx.Group.
Create().
SetName("Github").
Save(ctx)
if err != nil {
// Jika terjadi kesalahan, batalkan transaksi
return rollback(tx, fmt.Errorf("Gagal membuat Grup: %w", err))
}
// Operasi tambahan dapat ditambahkan di sini...
// Konfirmasi transaksi
tx.Commit()
4. Penanganan Kesalahan dan Pembatalan dalam Transaksi
4.1 Pentingnya Penanganan Kesalahan
Ketika bekerja dengan basis data, berbagai kesalahan seperti isu jaringan, konflik data, atau pelanggaran konstrain dapat terjadi kapan saja. Penanganan kesalahan dengan benar sangat penting untuk memelihara konsistensi data. Dalam sebuah transaksi, jika sebuah operasi gagal, transaksi harus dibatalkan untuk memastikan bahwa operasi-operasi yang sebagian diselesaikan, yang mungkin mengorbankan konsistensi basis data, tidak ditinggalkan.
4.2 Cara Melaksanakan Pembatalan
Dalam kerangka kerja ent
, Anda dapat menggunakan metode Tx.Rollback()
untuk membatalkan seluruh transaksi. Biasanya, sebuah fungsi bantuan rollback
didefinisikan untuk menangani pembatalan dan kesalahan, seperti yang ditunjukkan di bawah ini:
func rollback(tx *ent.Tx, err error) error {
if rerr := tx.Rollback(); rerr != nil {
// Jika pembatalan gagal, kembalikan kesalahan asli dan kesalahan pembatalan bersama-sama
err = fmt.Errorf("%w: Kesalahan terjadi saat pembatalan transaksi: %v", err, rerr)
}
return err
}
Dengan fungsi rollback
ini, kita dapat dengan aman menangani kesalahan dan pembatalan transaksi saat operasi apa pun dalam transaksi gagal. Hal ini memastikan bahwa bahkan dalam kejadian sebuah kesalahan, itu tidak akan memiliki dampak negatif pada konsistensi basis data.
5. Penggunaan Klien Transaksional
Dalam aplikasi praktis, mungkin ada skenario di mana kita perlu dengan cepat mengubah kode non-transaksional menjadi kode transaksional. Untuk kasus seperti itu, kita dapat menggunakan klien transaksional untuk dengan mulus bermigrasi kode tersebut. Berikut adalah contoh bagaimana mengubah kode klien non-transaksional yang sudah ada untuk mendukung transaksi:
// Pada contoh ini, kita membungkus fungsi Gen asli dalam sebuah transaksi.
func WrapGen(ctx context.Context, client *ent.Client) error {
// Pertama, buat transaksi
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// Dapatkan klien transaksional dari transaksi
txClient := tx.Client()
// Jalankan fungsi Gen menggunakan klien transaksional tanpa mengubah kode Gen asli
if err := Gen(ctx, txClient); err != nil {
// Jika terjadi kesalahan, batalkan transaksi
return rollback(tx, err)
}
// Jika berhasil, commit transaksi
return tx.Commit()
}
Pada kode di atas, klien transaksional tx.Client()
digunakan, memungkinkan fungsi Gen
asli dieksekusi dengan jaminan transaksi. Pendekatan ini memungkinkan kita untuk dengan mudah mengubah kode non-transaksional yang sudah ada menjadi kode transaksional dengan dampak minimal pada logika asli.
6. Praktik Terbaik untuk Transaksi
6.1 Mengelola Transaksi dengan Fungsi Callback
Ketika logika kode kita menjadi kompleks dan melibatkan beberapa operasi basis data, manajemen terpusat dari operasi-operasi tersebut dalam sebuah transaksi menjadi sangat penting. Berikut adalah contoh pengelolaan transaksi melalui fungsi callback:
func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
// Gunakan defer dan recover untuk menangani skenario panic yang potensial
defer func() {
if v := recover(); v != nil {
tx.Rollback()
panic(v)
}
}()
// Panggil fungsi callback yang disediakan untuk menjalankan logika bisnis
if err := fn(tx); err != nil {
// Jika terjadi kesalahan, batalkan transaksi
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: rolling back transaction: %v", err, rerr)
}
return err
}
// Jika logika bisnis tidak bermasalah, commit transaksi
return tx.Commit()
}
Dengan menggunakan fungsi WithTx
untuk membungkus logika bisnis, kita dapat memastikan bahwa bahkan jika kesalahan atau pengecualian terjadi dalam logika bisnis, transaksi akan ditangani dengan benar (entah di-commit atau di-rollback).
6.2 Menggunakan Hook Transaksi
Sama seperti hook skema dan hook runtime, kita juga dapat mendaftarkan hook dalam transaksi aktif (Tx) yang akan dipicu saat Tx.Commit
atau Tx.Rollback
:
func Do(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
tx.OnCommit(func(next ent.Committer) ent.Committer {
return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
// Logika sebelum meng-commit transaksi
err := next.Commit(ctx, tx)
// Logika setelah meng-commit transaksi
return err
})
})
tx.OnRollback(func(next ent.Rollbacker) ent.Rollbacker {
return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {
// Logika sebelum melakukan rollback transaksi
err := next.Rollback(ctx, tx)
// Logika setelah melakukan rollback transaksi
return err
})
})
// Jalankan logika bisnis lainnya
//
//
//
return err
}
Dengan menambahkan hook selama commit transaksi dan rollback, kita dapat menangani logika tambahan, seperti logging atau pembersihan sumber daya.
7. Memahami Berbagai Tingkat Isolasi Transaksi
Dalam sistem basis data, pengaturan tingkat isolasi transaksi sangat penting untuk mencegah berbagai isu konkurensi (seperti bacaan kotor, bacaan yang tidak dapat diulangi, dan bacaan maya). Berikut adalah beberapa tingkat isolasi standar dan cara mengaturnya dalam kerangka kerja ent
:
- READ UNCOMMITTED: Tingkat terendah, memungkinkan pembacaan perubahan data yang belum di-commit, yang dapat menyebabkan bacaan kotor, bacaan yang tidak dapat diulangi, dan bacaan maya.
- READ COMMITTED: Memungkinkan pembacaan dan peng-commit-an data, mencegah bacaan kotor, namun bacaan yang tidak dapat diulangi dan bacaan maya masih dapat terjadi.
- REPEATABLE READ: Memastikan bahwa membaca data yang sama berkali-kali dalam transaksi yang sama menghasilkan hasil yang konsisten, mencegah bacaan yang tidak dapat diulangi, namun bacaan maya masih dapat terjadi.
- SERIALIZABLE: Tingkat isolasi yang paling ketat, berusaha mencegah bacaan kotor, bacaan yang tidak dapat diulangi, dan bacaan maya dengan mengunci data yang terlibat.
Dalam ent
, jika driver basis data mendukung pengaturan tingkat isolasi transaksi, dapat diatur sebagai berikut:
// Atur tingkat isolasi transaksi menjadi repeatable read
tx, err := client.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
Memahami tingkat isolasi transaksi dan aplikasinya dalam basis data sangat penting untuk memastikan konsistensi data dan stabilitas sistem. Pengembang harus memilih tingkat isolasi yang sesuai berdasarkan persyaratan aplikasi spesifik untuk mencapai praktik terbaik dalam memastikan keamanan data dan mengoptimalkan kinerja.