Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use database transactions in Laravel

2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Editor to share with you how to use database transactions in Laravel, I hope you will learn something after reading this article, let's discuss it together!

What is a database transaction?

Before we start looking at Laravel's database transactions, let's take a look at what they are and how useful they are.

There are many complex-sounding technical explanations for what database transactions are. For most web developers, however, all we need to know is that transactions are the way to complete the entire unit of work in the database.

To understand what this actually means, let's look at a basic example that will give a little context.

Suppose we have an application that allows users to register. Whenever users sign up, we want to create a new account for them and then assign them a default role "general".

Our code might look like this:

$user = User::create (['email' = > $request- > email,]); $user- > roles ()-> attach (Role::where (' name', 'general')-> first ())

At first glance, this code seems perfectly fine. However, when we look closely, we can find that there are actually some things that can go wrong. We can create users, but we cannot assign roles to them. This may be caused by many different reasons, such as errors in the code that assigns roles, or even hardware problems that prevent us from reaching the database.

As this happens, this will mean that there will be a user without a role in the system. As you can imagine, this can cause exceptions and bug elsewhere in your application, because you always assume that the user has a role (which is correct).

Therefore, to solve this problem, we can use database transactions. By using transactions, it ensures that any changes made to the database within the transaction will be rolled back if any errors occur when the code is executed. For example, if the user is inserted into the database, but the query to assign roles fails for any reason, the transaction will be rolled back and the user row will be deleted. By doing so, it means that we cannot create users who are not assigned roles.

In other words, it "has all or nothing".

Using database transactions in Laravel

Now that we have a simple idea of what transactions are and what they implement, let's look at how to use them in Laravel.

In Laravel, because we can access the transaction () method on the DB facade, it's actually easy to start using transactions. Continuing with the previous sample code, let's look at how transactions are used when creating users and assigning them roles.

Use Illuminate\ Support\ Facades\ DB;DB::transaction (function () use ($user, $request): void {$user = User::create (['email' = > $request- > email,]); $user- > roles ()-> attach (Role::where (' name', 'general')-> first ();})

Now that our code is wrapped in a database transaction, if an exception is thrown at any point in it, any changes to the database will return to the state before the transaction started.

Manually using database transactions in Laravel

Sometimes, you may want to have finer control over transactions. For example, suppose you are integrating with third-party services, such as Mailchinp or Xero. We will say that every time you create a new user, you also need to issue an HTTP request to their API to create them as users in the system.

We may want to update our code so that if we are unable to create users in our own systems and in third-party systems, neither system will create users. If you are interacting with a third-party system, you may have a class that can be used to make requests. Or, there may be a package that you can use. Sometimes, when some request cannot be completed, the class that made the request may throw an exception. However, some of these classes may eliminate errors and simply return false from the method you call and place the error in the field of the class.

Therefore, let's assume that we have the following basic sample class that calls API:

Class ThirdPartyService {private $errors; public function createUser ($userData) {$request = $this- > makeRequest ($userData); if ($request- > successful ()) {return $request- > body ();} $errors = $request- > errors (); return false;} public function getErrors () {return $this- > errors;}}

Of course, the above request class code is incomplete, and my following code example is not very clear, but it should give you an overview of what I'm trying to say. So let's use this request class and add it to our previous code example:

Use Illuminate\ Support\ Facades\ DB;use App\ Services\ ThirdPartyService;DB::beginTransaction (); $thirdPartyService = new ThirdPartyService (); $userData = ['email' = > $request- > email,]; $user = User::create ($userData); $user- > roles ()-> attach (Role::where (' name', 'general')-> first ()); if ($thirdPartyService- > createUser ($userData)) {DB::commit (); return;} DB::rollBack (); report ($thirdPartyService- > getErrors ())

Looking at the code above, we can see that we started a transaction, created users and assigned them a role, and then we called the third-party service. If we successfully create a user in an external service and know that everything has been created correctly, we can safely commit database changes. However, if you do not create a user in an external service, roll back the changes in the database (delete the user and their role assignments) and report the error.

Skills for interacting with third-party services

As an additional technique, I usually recommend putting any code that affects third-party systems, file storage, or caching after database calls.

To understand this more deeply, let's take the above code example as an example. Notice how we first made all changes to the database before making a request to a third-party service. This means that if any errors are returned from a third-party request, the user and role assignments in our own database will be rolled back.

However, this is not the case if we do the opposite and make a request before modifying the database. For any reason, if we make any error in creating a user in the database, we will create a new user in a third-party system, but not in our system. As you might think, this may lead to more problems. The severity of the problem can be reduced by writing a cleanup method to remove the user from the third-party system. However, as you can imagine, this can lead to more problems and lead to writing, maintaining, and testing more code.

Therefore, I always recommend putting the database call before the API call. However, this is not always the case, and sometimes it may be necessary to save the values returned by third-party requests to the database. If this is the case, you need to put the API call before the database call, as long as you make sure that there is some code that can handle any failure.

Use automatic or manual transactions

It is also worth noting that because our initial example uses the DB:transaction () method to roll back the transaction when an exception is thrown, we can also use this method to make a request to our third-party service. Instead, we can update the class like this:

Use Illuminate\ Support\ Facades\ DB;use App\ Services\ ThirdPartyService;DB::transaction (function () use ($user, $request): void {$user = User::create (['email' = > $request- > email,]); $user- > roles ()-> attach (Role::where (' name', 'general')-> first (); if (! $thirdPartyService- > createUser ($userData)) {throw new\ Exception (' User could not be created');}})

This is definitely a viable solution and will roll back the transaction successfully as expected. In fact, as far as my personal preference is concerned, I actually prefer this approach to using transactions manually. I think it looks easier to read and understand.

However, exception handling can be more expensive in terms of time and performance than using the 'if' statement when manually committing or rolling back a transaction.

So, for example, if this code is used to import a CSV file that contains 10000 user data, you may find that throwing an exception greatly slows down the import.

However, if it is only used in a simple web request that the user can register, there may be no problem throwing an exception. Of course, it depends on the size of the application, and performance is a key factor; so you need to decide on a case-by-case basis.

Scheduling queues in database transactions

Whenever you deal with queues in a transaction, you need to pay attention to a "trap".

To provide some context, let's continue with the previous code example. We can imagine that after we have created our users, we want to run a task to remind administrators to notify them of new registrations and send welcome emails to new users. We will do this by assigning a queue task named AlertNewUser, as follows:

Use Illuminate\ Support\ Facades\ DB;use App\ Jobs\ AlertNewUser;use App\ Services\ ThirdPartyService;DB::transaction (function () use ($user, $request): void {$user = User::create (['email' = > $request- > email,]); $user- > roles ()-> attach (Role::where (' name', 'general')-> first ()); AlertNewUser::dispatch ($user);})

When you start a transaction and make changes to any of the data in it, these changes are available only to requests / processes that are running the transaction. For any other request or process to access the data you changed, you must first commit the transaction. Therefore, this means that if we dispatch any queued queues, event listeners, mail, notifications, or broadcast events from within the transaction. Due to competitive conditions, our data changes may not be available within the transaction.

This happens if the queue starts processing the queued code before the transaction commits. Therefore, this may cause your queuing code to try to access data that does not exist and may cause errors. In our example, if we run a queued AlertNewUser job before the transaction is committed, our job will try to access a user who is not actually stored in the database. As you might expect, this will cause the job to fail.

To prevent this race condition, we can make some changes to our code and / or our configuration to ensure that the queue is scheduled only after the transaction is successfully committed.

We can update the config/queue.php and add the after commit field. Let's imagine that we are using the redis queue driver, so we can update the configuration like this:

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report