Waktu pertama kali bikin fitur yang melibatkan banyak operasi database sekaligus, saya sempat khawatir: “Kalau satu langkah gagal, data bakal berantakan nggak ya?” Setelah pakai Database Transaction, kekhawatiran itu hilang karena semua proses jadi berjalan lebih aman dan konsisten.
Kenapa Perlu Transaction?
Sebagai contoh. misalkan pada fitur top-up atau transfer saldo
// Tanpa Transaction - BERBAHAYA!
$sender = User::find(1);
$sender->balance -= 100000;
$sender->save();
// Tiba-tiba error di sini! Server crash atau apa...
// Uang udah kepotong tapi belum masuk ke penerima
$receiver = User::find(2);
$receiver->balance += 100000;
$receiver->save();
Dengan transaction, kalau ada error di mana aja, semuanya bakal di-rollback
Penggunaan Transaction di Laravel
Laravel punya beberapa metode penggunaan transaction, disini saya contohkan yang sering saya temui
1. Cara Otomatis (Sederhana)
Ini penulisan sederhana. Laravel akan otomatis rollback kalau ada exception.
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
$sender = User::find(1);
$sender->balance -= 100000;
$sender->save();
$receiver = User::find(2);
$receiver->balance += 100000;
$receiver->save();
// Kalau ada error di mana aja, otomatis rollback!
});
Cukup wrap operasi database dalam closure DB::transaction().
2. Cara Manual (Lebih Fleksibel)
Kalau butuh kontrol lebih, bisa pakai cara manual dengan beginTransaction, commit, dan rollback. Ini metode penulisan yang sering saya gunakan karena menurut saya ini yang lebih mudah dipahami
use Illuminate\Support\Facades\DB;
DB::beginTransaction();
try {
$sender = User::find(1);
$sender->balance -= 100000;
$sender->save();
$receiver = User::find(2);
$receiver->balance += 100000;
$receiver->save();
DB::commit(); // Kalau semua oke, commit
} catch (\Exception $e) {
DB::rollBack(); // Kalau ada error, rollback
// Handle error
return response()->json(['error' => $e->getMessage()], 500);
}
3. Return Value dari Transaction
Kamu juga bisa return value dari transaction closure:
$result = DB::transaction(function () {
$order = Order::create([
'user_id' => 1,
'total' => 150000
]);
$product = Product::find(10);
$product->stock -= 1;
$product->save();
return $order; // Return order yang baru dibuat
});
return response()->json($result);
Contoh real-case sederhana
Transfer Saldo
public function transfer(Request $request)
{
$validated = $request->validate([
'receiver_id' => 'required|exists:users,id',
'amount' => 'required|numeric|min:1000'
]);
DB::transaction(function () use ($validated) {
$sender = auth()->user();
$receiver = User::findOrFail($validated['receiver_id']);
$amount = $validated['amount'];
// Validasi saldo cukup
if ($sender->balance < $amount) {
throw new \Exception('Saldo tidak cukup');
}
// Kurangi saldo pengirim
$sender->balance -= $amount;
$sender->save();
// Tambah saldo penerima
$receiver->balance += $amount;
$receiver->save();
// Catat history
Transaction::create([
'sender_id' => $sender->id,
'receiver_id' => $receiver->id,
'amount' => $amount,
'type' => 'transfer'
]);
});
return response()->json(['message' => 'Transfer berhasil']);
}
Checkout Order
public function checkout(Request $request)
{
$order = DB::transaction(function () use ($request) {
// Buat order
$order = Order::create([
'user_id' => auth()->id(),
'total' => $request->total,
'status' => 'pending'
]);
// Loop items
foreach ($request->items as $item) {
$product = Product::findOrFail($item['product_id']);
// Cek stock
if ($product->stock < $item['quantity']) {
throw new \Exception("Stock {$product->name} tidak cukup");
}
// Kurangi stock
$product->stock -= $item['quantity'];
$product->save();
// Buat order item
OrderItem::create([
'order_id' => $order->id,
'product_id' => $product->id,
'quantity' => $item['quantity'],
'price' => $product->price
]);
}
return $order;
});
return response()->json($order);
}
Delete User dengan Relasi
public function deleteUser($userId)
{
DB::transaction(function () use ($userId) {
$user = User::findOrFail($userId);
// Hapus semua posts user
$user->posts()->delete();
// Hapus semua comments user
$user->comments()->delete();
// Hapus user
$user->delete();
});
return response()->json(['message' => 'User berhasil dihapus']);
}
Retry Transaction
Laravel juga punya fitur retry. Kalau transaction gagal karena deadlock, bisa otomatis retry.
DB::transaction(function () {
// operasi database
}, 5); // Retry 5x kalau gagal
Nested Transaction
Laravel support nested transaction dengan savepoint. Tapi honestly, sebisa mungkin hindari nested transaction karena bikin kompleks dan sulit untuk dipahami kodenya.
DB::transaction(function () {
User::create([...]);
DB::transaction(function () {
Post::create([...]);
});
});
Kapan Pakai Transaction?
✅ Wajib Pakai:
- Transfer uang/saldo
- Checkout/order dengan multiple items
- Update data yang saling bergantung
- Delete dengan cascade (hapus user + posts + comments)
- Create dengan relasi (create order + order items)
❌ Nggak Perlu:
- Single insert/update yang independent
- Read-only operations
- Operasi yang nggak related
Kesalahan yang kadang terjadi
1. Lupa Rollback
// ❌ Bad
DB::beginTransaction();
try {
// operasi
DB::commit();
} catch (\Exception $e) {
// Lupa rollback!
return response()->json(['error' => $e->getMessage()]);
}
2. Transaction Terlalu Besar
// ❌ Bad
DB::transaction(function () {
// 100 operasi database
// Loop ratusan data
// Proses berat
});
3. Catch Exception Tanpa Re-throw
// ❌ Bad
DB::transaction(function () {
try {
// operasi yang bisa error
} catch (\Exception $e) {
// Catch tapi nggak di-throw lagi
// Transaction tetap commit!
}
});
Database transaction di Laravel itu sederhana, namun ada beberapa hal yang perlu diingat:
- Pakai
DB::transaction()untuk auto-handling - Pakai
beginTransaction(),commit(),rollback()kalau butuh kontrol lebih - Validasi dulu sebelum masuk transaction
- Jangan lupa error handling
Dengan penggunaan transaction ini, operasi data yang dijalan akan lebih konsisten dan aman jika ada proses yang gagal.