Quay lại

Laravel transactions và các trường hợp sử dụng Chuyên mục PHP và Laravel    2024-02-23    41 Lượt xem    27 Lượt thích    comment-3 Created with Sketch Beta. 0 Bình luận

Laravel transactions và các trường hợp sử dụng

Như ở bài trước Transaction là gì? và cách thức hoạt động của Transaction trong SQL mình đã nói rõ cho các bạn hiểu hơn về transaction trong SQL ở bài này mình chỉ khái quát lại và áp dụng nó vào Laravel để các bạn có một hình dung và áp dụng trong thực tế của các bạn nhé! Như các bạn biết trong thế giới phát triển web động, việc duy trì tính toàn vẹn của dữ liệu là rất quan trọng và Laravel là một trong những công cụ đảm bảo điều đó. Nhưng transaction Laravel là gì? Tại sao chúng không thể thiếu? Làm thế nào bạn có thể sử dụng sức mạnh của chúng? Hãy chuẩn bị sẵn sàng, khi chúng ta bắt đầu hành trình vào vương quốc của các transaction thôi.

Laravel transaction là gì?

Trong Laravel, một transaction là một nhóm các truy vấn SQL được thực thi như một đơn vị duy nhất, trong đó mọi thay đổi trong cơ sở dữ liệu chỉ được lưu trữ nếu tất cả các truy vấn trong transaction đều thành công. Nếu có bất kỳ lỗi nào xảy ra trong quá trình thực hiện transaction, tất cả các thay đổi sẽ bị hủy và cơ sở dữ liệu sẽ trở về trạng thái ban đầu.

Transaction là một cách để đảm bảo tính nhất quán của dữ liệu trong cơ sở dữ liệu, đặc biệt là trong các tình huống mà có nhiều truy vấn phải được thực thi một cách an toàn và không bị ảnh hưởng bởi các lỗi hoặc tình huống không mong muốn.

Trong Laravel, bạn có thể sử dụng các phương thức như beginTransaction(), commit(), và rollback() để bắt đầu, hoàn thành và hủy bỏ một transaction. Sử dụng transaction trong Laravel giúp cho việc quản lý cơ sở dữ liệu trở nên dễ dàng và đảm bảo tính nhất quán của dữ liệu trong các ứng dụng web phức tạp.

Tại sao chúng ta cần sử dụng chúng?

Giả sử bạn đang phát triển một ứng dụng ngân hàng trực tuyến. Khi một khách hàng chuyển tiền từ tài khoản của mình đến tài khoản khác, có hai hoạt động cơ sở dữ liệu cần được thực hiện: giảm số dư của tài khoản nguồn và tăng số dư của tài khoản đích.

Nếu hệ thống gặp sự cố sau khi giảm số dư của tài khoản nguồn nhưng trước khi tăng số dư của tài khoản đích, dữ liệu sẽ rơi vào tình trạng không nhất quán. Trong trường hợp này, người dùng sẽ mất tiền từ tài khoản của mình mà không nhận được số dư tương ứng trong tài khoản đích, gây ra mất mát không mong muốn và không nhất quán trong cơ sở dữ liệu.

Bằng cách sử dụng transaction, bạn có thể đảm bảo rằng cả hai hoạt động cơ sở dữ liệu (giảm số dư của tài khoản nguồn và tăng số dư của tài khoản đích) được thực hiện hoàn toàn hoặc không có gì xảy ra. Nếu có bất kỳ sự cố nào xảy ra giữa quá trình này, transaction sẽ bị hủy và cả hai hoạt động sẽ được rollback, đảm bảo tính nhất quán của dữ liệu và tránh mất mát không mong muốn cho người dùng. Điều này làm cho việc sử dụng transaction trở nên rất quan trọng trong các ứng dụng nơi tính toàn vẹn dữ liệu là điều cần thiết.

Làm thế nào để sử dụng chúng?

Hãy cùng nhìn sâu vào việc triển khai Laravel transaction, lưu ý rằng ví dụ được đơn giản hóa để dễ hiểu hơn:

DB::beginTransaction();

//Handle Exceptions
try {
    // Perform multiple database operations, including updates, inserts, and deletions.
    $product->update(['stock' => $product->stock - 1]);

    $user->update(['balance' => $user->balance - $price]);

} catch(Throwble $e) {
    // In case of an exception we rollback all the queries
    DB::rollback();
    throw $e;
}

// If everything goes right, we save the changes to database
DB::commit();

Trong ví dụ trên, mình đã thể hiện cách sử dụng Laravel transaction một cách chi tiết và toàn diện. Điều này được thực hiện để đảm bảo một hiểu biết rõ ràng về từng bước trong quá trình. Tuy nhiên, có một phương pháp đơn giản hơn để đạt được cùng chức năng bằng cách sử dụng Closure (Closure-Based Approach) hàm DB::transaction, cung cấp một cách tiếp cận trực quan hơn để thực hiện nhiệm vụ.

DB::transaction(function () use ($product, $user, $price) {
    $product->decrement('stock');
    $user->decrement('balance', $price);
});

Ứng dụng retry Transaction trong Laravel

Như mình đã trình bày ở trên thì Laravel hỗ trợ 2 kiểu Transaction. bản chất cả 2 cách này đều có thể apply được Retry. Đơn giản chỉ là cho chạy vòng lặp lại Transaction đó nếu có exception xảy ra, tối đa x lần. Laravel đã hỗ trợ:

DB::transaction(function () use ($product, $user, $price) {
    $product->decrement('stock');
    $user->decrement('balance', $price);
}, 5);

thực hiện retry Transaction 5 lần. Nếu ae muốn log lại mỗi lần bị DeadLock, hay log lại số lần phải reTry thì có thể custom lại như sau, đoạn code trên sẽ tương tự như:

for	($currentAttempt = 0; $currentAttempt < 5; $currentAttempt++) {
    DB::beginTransaction();

    //Handle Exceptions
    try {
        // Perform multiple database operations, including updates, inserts, and deletions.
        $product->update(['stock' => $product->stock - 1]);

        $user->update(['balance' => $user->balance - $price]);

        // If everything goes right, we save the changes to database
        DB::commit();

    } catch(Throwble $e) {
        // In case of an exception we rollback all the queries
        DB::rollback();
        // Nếu exception là DeadLock thì mới retry!!
    	if ($this->causedByDeadlock($e) && $currentAttempt < $attempts) {
   			// ToDo SomeThing (Log,...)
    		continue;
    	}
        throw $e;
    }
}

Để tránh tình trạng kéo performance đi xuống thì logic business cố gắng để hết ngoài Transaction, trong Transaction chỉ thực thi truy vấn. Nếu để hết vào trong Transaction, hệ thống sẽ chạy lại hàm tính toán mất thêm nhiều thời gian. Trong khi mục đích của mình chỉ là thực thi câu truy vấn.

Các trường hợp sử dụng thực tế

1. Đăng ký một công ty mới với người dùng mặc định.

Hãy tưởng tượng bạn có một ứng dụng mà người dùng có thể tạo ra một công ty. Bạn muốn tự động tạo ra một người dùng đặc biệt, giống như một quản trị viên, người có thể kiểm soát đầy đủ công ty đó. Điều này có nghĩa là, khi công ty được tạo ra, người dùng cũng được tạo ra. Nhưng nếu việc tạo người dùng gặp vấn đề, quá trình tạo công ty cũng sẽ được quay trở lại. Điều này ngăn chặn việc có một công ty mà không có người dùng trong hệ thống của bạn.

DB::transaction(function () {
    // Creating a new company
    $company = Company::create(['name' => 'New Company']);

    // Creating a default user for the company
    $user = $company->users()->create(['name' => 'Default User']);
});

2. Database tests với transactions

Khi bạn đang test và sử dụng một cơ sở dữ liệu thực, bạn có thể sử dụng transaction để hoàn tác tất cả các thay đổi bạn thực hiện trong quá trình thử nghiệm. Điều này đảm bảo rằng mỗi bài kiểm tra bắt đầu với một cơ sở dữ liệu mới và chỉ có dữ liệu bạn thiết lập cho bài kiểm tra đó được tồn tại khi bài kiểm tra chạy.

use Illuminate\Foundation\Testing\RefreshDatabase;

class YourTest extends TestCase
{
    use RefreshDatabase;

    public function testSomething()
    {
        // Test code within the transaction
        DB::transaction(function () {
            // Your test logic here
        });
    }
}

3. Many-to-Many relationship với transaction

Một tình huống khác mà bạn muốn sử dụng transaction là khi xử lý một mối quan hệ nhiều-nhiều. Bạn muốn đảm bảo rằng các dữ liệu bạn thêm vào — như các nhãn (tags), ví dụ — được liên kết đúng cách với bài viết trong hệ thống của bạn.

DB::transaction(function () {
    $post = Post::create(['title' => 'Sample Post']);
    $tag1 = Tag::create(['name' => 'Tag1']);
    $tag2 = Tag::create(['name' => 'Tag2']);

    // Attaching tags to the post
    $post->tags()->attach([$tag1->id, $tag2->id]);
});

4. Deleting users info với transactions

Ở đây, chúng ta đang loại bỏ một người dùng và các thông tin liên quan của họ. Nếu bất kỳ bước nào gặp sự cố, chẳng hạn như không xóa bình luận hoặc bài viết một cách chính xác, tất cả mọi thứ sẽ được quay ngược lại. Điều này ngăn chặn việc có thông tin người dùng không hoàn chỉnh hoặc lộn xộn, và tránh hiển thị các bài viết không có tác giả trên một trang.

DB::transaction(function () {
    $user = User::find(1);

    if ($user) {
        // Delete user info manually
        $user->comments()->delete();
        $user->posts()->delete();
        // ... Delete other related data

        // Delete the user
        $user->delete();
    }
});

5. E-commerce example với kiểm tra lỗi tùy chỉnh

DB::beginTransaction();

try {
    $product->decrement('stock');
    $user->decrement('balance', $price);

} catch(ValidationException $e) {
    DB::rollback();
    return Redirect::to('/form')
        ->withErrors($e->getErrors())
        ->withInput();

} catch(Throwable $e) {
    DB::rollback();
    throw $e;
}

DB::commit();
Ở bài tiếp theo chúng ta sẽ đi đến một số khái niệm nâng cao hơn các bạn xem link bên dưới nhé!

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday