Laravel Database Transaction: Mencegah Perubahan Data Parsial

5 Oktober 2026 · 6 menit baca

Artikel ini dibuat secara otomatis dengan bantuan AI dan telah ditinjau oleh Arya Anjar Ginantara.

Editorial cover for Laravel Database Transaction: Mencegah Perubahan Data Parsial.

Ketika satu operasi bisnis menulis beberapa record, kegagalan di langkah terakhir dapat meninggalkan database dalam keadaan setengah jadi. Misalnya, proses konfirmasi pesanan sudah membuat order dan mengurangi stok, tetapi pencatatan pembayaran gagal. Tanpa batas transaksi yang jelas, dua perubahan pertama bisa tetap tersimpan meskipun operasi secara keseluruhan belum berhasil.

Di Laravel, DB::transaction membantu mengelompokkan beberapa operasi database menjadi satu unit kerja: jika closure selesai tanpa exception, transaksi di-commit; jika exception dilempar dari closure, transaksi di-rollback dan exception tersebut dilempar kembali. Pola ini membantu menjaga agar perubahan database untuk satu operasi tidak berhenti di tengah—tetapi hanya untuk perubahan yang memang berada di database.

Kapan operasi perlu dibungkus transaksi?

Gunakan transaksi ketika beberapa penulisan database harus diperlakukan sebagai satu keputusan bisnis. Contohnya:

  • membuat order dan mencatat item order;
  • mengurangi stok dan menyimpan riwayat perubahan stok;
  • mencatat perpindahan saldo serta jurnalnya.

Intinya bukan jumlah query, melainkan hubungan antarperubahannya. Jika satu perubahan boleh tersimpan tanpa perubahan lain, keduanya mungkin tidak perlu berada dalam transaksi yang sama. Sebaliknya, jika keberhasilan operasi bergantung pada seluruh perubahan tersebut, tentukan batas transaksi di service atau method yang mewakili operasi bisnis itu.

Transaksi bukan pengganti validasi, aturan domain, desain schema, authorization, atau pengujian aplikasi. Transaksi hanya mengatur unit perubahan pada database sesuai dukungan koneksi dan database yang digunakan aplikasi.

Contoh: konfirmasi pesanan sebagai satu operasi

Berikut ilustrasi service yang membuat order, menyimpan item, dan mengurangi stok dalam satu transaksi. Nama model dan atribut hanya contoh; sesuaikan dengan aplikasi Anda.

<?php

namespace App\Services;

use App\Models\InventoryItem;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class ConfirmOrder
{
    /**
     * Documentation-guided illustration; not executed in this article.
     */
    public function handle(int $customerId, array $lines): Order
    {
        return DB::transaction(function () use ($customerId, $lines) {
            $order = Order::create([
                'customer_id' => $customerId,
                'status' => 'confirmed',
            ]);

            foreach ($lines as $line) {
                $stock = InventoryItem::query()
                    ->where('sku', $line['sku'])
                    ->first();

                if ($stock === null || $stock->available < $line['quantity']) {
                    throw new RuntimeException('Stok tidak mencukupi.');
                }

                $order->items()->create([
                    'sku' => $line['sku'],
                    'quantity' => $line['quantity'],
                ]);

                $stock->decrement('available', $line['quantity']);
            }

            return $order;
        });
    }
}

Batas pentingnya ada pada closure yang diberikan ke DB::transaction. Pembuatan order, pembuatan item, dan pengurangan stok berada di dalam closure yang sama. Jika pengecekan stok melempar exception setelah order dibuat, Laravel akan membatalkan perubahan database yang sudah dilakukan dalam transaksi tersebut, lalu melempar kembali exception agar lapisan pemanggil dapat menanganinya.

Contoh ini bukan bukti bahwa model, schema, aturan stok, atau alur aplikasi tertentu sudah benar. Ia hanya menunjukkan bentuk batas transaksi. Kode juga belum dijalankan terhadap project, koneksi database, model, atau data nyata.

Apa yang terjadi saat operasi gagal?

Ada dua hasil utama:

  1. Closure selesai tanpa exception. Laravel melakukan commit terhadap transaksi.
  2. Exception dilempar di dalam closure. Laravel melakukan rollback, lalu melempar kembali exception tersebut.

Karena itu, jangan menangkap exception di dalam closure lalu menganggap operasi berhasil jika Anda masih ingin transaksi dibatalkan. Jika exception perlu ditangani, pastikan keputusan untuk melanjutkan atau menggagalkan operasi tetap jelas. Penanganan di luar pemanggilan transaksi sering lebih mudah dibaca karena batas rollback tetap terlihat:

try {
    $order = $this->confirmOrder->handle($customerId, $lines);
} catch (\Throwable $e) {
    // Dokumentasi-guided illustration; sesuaikan dengan error handling aplikasi.
    report($e);

    throw $e;
}

DB::transaction mengelola commit dan rollback untuk query builder maupun operasi Eloquent yang memakai koneksi transaksi tersebut. Itu tidak berarti semua pekerjaan yang dipicu dari closure menjadi bagian dari rollback database.

Retry untuk deadlock bukan jaminan bebas deadlock

Laravel mendokumentasikan argumen kedua pada DB::transaction untuk menentukan berapa kali transaksi dicoba ulang ketika terjadi deadlock. Setelah jumlah percobaan habis, exception akan dilempar. Contoh dokumentasi dengan lima percobaan dapat ditulis menggunakan argumen posisi:

DB::transaction(function () use ($customerId, $lines) {
    // Operasi database untuk satu unit bisnis.
}, 5);

Nilai 5 di sini adalah kebijakan contoh, bukan angka universal. Jumlah retry perlu dipertimbangkan bersama karakteristik operasi, koneksi, database engine, batas waktu, dan cara aplikasi menangani exception. Retry juga tidak membuktikan bahwa operasi aman untuk diulang tanpa syarat. Pastikan operasi dan aturan domain Anda memahami kemungkinan pemanggilan ulang sebelum mengandalkan retry.

Laravel 13.x juga mengingatkan bahwa named arguments tidak tercakup dalam jaminan kompatibilitas mundur framework. Karena itu, artikel ini memakai argumen posisi untuk contoh retry; selalu cocokkan sintaks dengan dokumentasi dan versi aplikasi yang sedang digunakan.

Database transaction tidak membatalkan email atau API eksternal

Batas DB::transaction adalah database. Jika closure mengirim email, menulis file, memanggil API pembayaran, mengirim job, atau memicu layanan lain, rollback database tidak otomatis membatalkan efek eksternal tersebut.

Misalnya, bila email dikirim di tengah closure lalu penulisan berikutnya gagal, email itu tidak dapat ditarik kembali hanya karena database di-rollback. Sebaliknya, jika Anda menaruh pemanggilan API eksternal di dalam transaksi, durasi transaksi dan urutan kegagalan dapat menjadi lebih sulit dipahami tanpa otomatis membuat kedua sistem konsisten.

Pisahkan pertanyaan berikut saat merancang operasi:

  • perubahan database apa yang harus berhasil atau gagal bersama;
  • efek eksternal apa yang boleh dipicu setelah commit;
  • bagaimana aplikasi menangani efek yang sudah terlanjur dikirim ketika proses lanjutan gagal.

Pola seperti job setelah commit, pencatatan event untuk diproses kemudian, atau outbox dapat menjadi pilihan desain, tetapi pemilihannya bergantung pada kebutuhan dan arsitektur aplikasi. Artikel ini tidak mengklaim salah satu pola tersebut otomatis menyelesaikan konsistensi lintas sistem.

Waspadai statement yang menyebabkan implicit commit

Dokumentasi Laravel memperingatkan bahwa statement tertentu di dalam transaksi dapat menyebabkan database melakukan implicit commit. Contoh yang disebutkan adalah statement untuk membuat tabel. Dalam kondisi seperti itu, database engine dapat melakukan commit tanpa Laravel mengetahui level transaksi yang sebenarnya.

Karena itu, jangan menjadikan transaksi aplikasi sebagai tempat untuk mencampur operasi perubahan schema atau statement lain yang berpotensi menyebabkan implicit commit. Untuk daftar statement yang berlaku, periksa dokumentasi database engine yang digunakan aplikasi. Cakupan dan perilakunya bukan sesuatu yang dapat digeneralisasi dari Laravel saja.

Checklist sebelum memilih batas transaksi

Sebelum menambahkan DB::transaction, jawab pertanyaan ini:

  • Apa satu operasi bisnis yang ingin dibuat atomic pada database?
  • Penulisan database mana yang wajib berhasil bersama?
  • Exception apa yang harus menggagalkan seluruh unit kerja?
  • Apakah ada email, queue, file, atau API eksternal di dalam closure?
  • Jika ada deadlock, apakah operasi dapat dicoba ulang dengan aman?
  • Apakah ada statement yang berpotensi menyebabkan implicit commit pada database engine Anda?
  • Bagaimana operasi ini diuji pada koneksi, model, schema, dan aturan domain yang sebenarnya?

Batas transaksi yang baik biasanya mengikuti batas keputusan bisnis, bukan sekadar membungkus sebanyak mungkin kode. Mulailah dari satu operasi kecil, jaga closure tetap mudah ditinjau, dan uji skenario sukses maupun exception pada project Anda sendiri sebelum menganggap perilakunya sesuai kebutuhan.

Bagikan:XLinkedIn

Artikel terkait