Rumah > Artikel > rangka kerja php > Mari kita bincangkan tentang cara menggunakan transaksi pangkalan data dengan betul dalam kod Laravel
Dalam pembangunan web, integriti dan ketepatan data adalah sangat penting. Oleh itu, kita mesti memastikan bahawa kod yang kita tulis boleh menyimpan, mengemas kini dan memadam data dalam pangkalan data dengan cara yang selamat.
Dalam artikel ini, kita akan melihat apakah transaksi pangkalan data, mengapa ia penting, dan cara mula menggunakannya dalam Laravel. Kami juga akan melihat "masalah" biasa yang melibatkan baris gilir dan transaksi pangkalan data.
Sebelum kita mula melihat transaksi pangkalan data dalam Laravel, mari kita lihat dahulu apa itu transaksi dan cara ia berfungsi Berguna .
Terdapat banyak penjelasan teknikal yang rumit tentang maksud transaksi pangkalan data. Walau bagaimanapun, bagi kebanyakan pembangun web, semua yang kita perlu tahu ialah urus niaga ialah bagaimana keseluruhan unit kerja dalam pangkalan data diselesaikan.
Untuk memahami maksud sebenarnya ini, mari kita lihat contoh asas yang akan memberikan sedikit konteks.
Andaikan kami mempunyai aplikasi yang membolehkan pengguna mendaftar. Setiap kali pengguna mendaftar, kami ingin membuat akaun baharu untuk mereka dan kemudian memberikan mereka peranan lalai "umum".
Kod kami mungkin kelihatan seperti ini:
$user = User::create([ 'email' => $request->email, ]); $user->roles()->attach(Role::where('name', 'general')->first());
Pada pandangan pertama, kod ini kelihatan baik-baik saja. Walau bagaimanapun, apabila kita melihat lebih dekat, kita dapat melihat bahawa sebenarnya terdapat beberapa perkara yang boleh menjadi salah. Kami boleh mencipta pengguna, tetapi kami tidak boleh memberikan peranan kepada mereka. Ini mungkin disebabkan oleh banyak sebab yang berbeza, seperti pepijat dalam kod yang memberikan peranan, atau malah isu perkakasan yang menghalang kami daripada mencapai pangkalan data.
Apabila ini berlaku, ini bermakna akan ada pengguna tanpa peranan dalam sistem. Seperti yang anda boleh bayangkan, ini boleh menyebabkan pengecualian dan pepijat di tempat lain dalam aplikasi anda, kerana anda sentiasa menganggap pengguna mempunyai peranan (yang betul).
Jadi, untuk menyelesaikan masalah ini, kita boleh menggunakan transaksi pangkalan data. Dengan menggunakan urus niaga, ia memastikan bahawa jika sebarang ralat berlaku semasa melaksanakan kod, sebarang perubahan pada pangkalan data di dalam urus niaga akan ditarik balik. Sebagai contoh, jika pengguna dimasukkan ke dalam pangkalan data, tetapi pertanyaan untuk menetapkan peranan gagal atas apa-apa sebab, maka transaksi akan ditarik balik dan baris pengguna akan dipadamkan. Dengan melakukan ini, ini bermakna kami tidak boleh membuat pengguna tanpa peranan yang ditetapkan.
Dalam erti kata lain, ia adalah "semua atau tiada".
Sekarang kita mempunyai idea mudah tentang transaksi itu dan apa yang mereka laksanakan, mari kita lihat bagaimana untuk menggunakannya dalam Laravel.
Dalam Laravel, sebenarnya mudah untuk mula menggunakan transaksi kerana kita boleh mengakses kaedah DB
pada transaction()
fasad. Meneruskan kod contoh sebelumnya, mari lihat cara urus niaga digunakan semasa membuat pengguna dan memberikan peranan kepada mereka.
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()); });
Kini kod kami dibalut dalam transaksi pangkalan data, dan jika pengecualian dilemparkan pada mana-mana titik di dalamnya, sebarang perubahan pada pangkalan data akan dikembalikan kepada keadaan sebelum transaksi dimulakan.
Kadangkala, anda mungkin mahukan kawalan yang lebih terperinci ke atas transaksi. Sebagai contoh, katakan anda menyepadukan dengan perkhidmatan pihak ketiga seperti Mailchinp atau Xero. Kami akan mengatakan bahawa apabila anda membuat pengguna baharu, anda juga perlu membuat permintaan HTTP kepada API mereka untuk mencipta mereka sebagai pengguna dalam sistem itu juga.
Kami mungkin ingin mengemas kini kod kami supaya jika kami tidak dapat mencipta pengguna dalam sistem kami sendiri ** dan ** dalam sistem pihak ketiga, kedua-dua sistem tidak mencipta pengguna. Jika anda berinteraksi dengan sistem pihak ketiga, anda mungkin mempunyai kelas yang boleh anda gunakan untuk membuat permintaan. Sebagai alternatif, mungkin terdapat pakej yang boleh anda gunakan. Kadangkala, apabila sesetengah permintaan tidak dapat dilengkapkan, kelas yang meminta mungkin membuang pengecualian. Walau bagaimanapun, sesetengah kelas ini mungkin menghapuskan ralat dan hanya mengembalikan false
daripada kaedah yang anda panggil, meletakkan ralat dalam medan kelas.
Jadi, mari kita anggap kita mempunyai kelas contoh asas berikut yang memanggil 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; } }
Sudah tentu, kod kelas permintaan di atas tidak lengkap dan contoh kod saya di bawah tidak begitu. jelas sama ada, tetapi ia sepatutnya memberi anda gambaran kasar tentang perkara yang saya cuba sampaikan. Jadi mari kita gunakan kelas permintaan ini dan tambahkannya pada contoh kod kami yang terdahulu:
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());
Melihat kod di atas, kita dapat melihat bahawa kita telah memulakan transaksi, mencipta pengguna dan menugaskan mereka Cipta peranan dan kemudian kami memanggil perkhidmatan pihak ketiga. Jika pengguna berjaya dibuat dalam perkhidmatan luaran, kami boleh melakukan perubahan pangkalan data dengan selamat dengan mengetahui bahawa semuanya telah dibuat dengan betul. Walau bagaimanapun, jika pengguna tidak dicipta dalam perkhidmatan luaran, perubahan dalam pangkalan data akan digulung semula (pengguna dan tugasan peranannya dipadamkan), dan kemudian ralat dilaporkan.
作为一个额外的技巧,我通常建议将任何影响第三方系统、文件存储或缓存的代码放在数据库调用之后。
为了更深入地理解这一点,让我们以上面的代码示例为例。请注意,在向第三方服务发出请求之前,我们是如何首先对数据库进行所有更改的。这意味着,如果从第三方请求返回任何错误,将回滚我们自己数据库中的用户和角色分配。
然而, 如果我们反过来做,我们在修改数据库之前发出请求,那就不是这种情况了。出于任何原因,如果我们在数据库中创建用户时发生任何错误,我们会在第三方系统中创建一个新用户,但是在我们系统中却没有创建。如你所想, 这可能会导致更多问题。通过编写一个清理方法将用户从第三方系统中删除,可以降低这个问题的严重性。 但是,正如您可以想象的那样, 这可能会导致更多的问题,并导致编写、维护和测试更多的代码。
所以,我总是建议把数据库调用放在API调用之前。但并不总是这样,有时可能需要将第三方请求返回的值保存到数据库中。如果是这种情况,就需要API调用放到数据库调用之前了,只要您确保有一些代码可以处理任何失败,这是完全可以的。
同样值得注意的是,因为我们最初的示例使用DB:transaction()
方法,在抛出异常时回滚事务,所以我们也可以使用这种方法向我们的第三方服务发出请求。相反,我们可以这样更新类:
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'); } });
这绝对是一个可行的解决方案,并将按照预期成功回滚事务。事实上,就我个人的偏好而言,我实际上更喜欢这种方式,而不是手动使用事务。我认为它看起来更容易阅读和理解。
然而,与手动提交或回滚事务时使用 ‘if’ 语句相比,异常处理在时间和性能方面可能会比较昂贵。
因此,举个例子,如果这段代码用于导入包含10,000个用户数据的 CSV 文件,您可能会发现抛出异常会大大减慢导入速度。
但是,如果它只是在一个用户可以注册的简单web请求中使用,那么抛出异常可能没有问题。当然,这取决于应用程序的大小,性能是关键因素;所以你需要根据具体情况来决定。
每当您在事务中处理队列时,您都需要注意一个“陷阱”。
为了提供一些上下文,让我们继续使用之前的代码示例。我们可以想象,在我们创建了我们的用户之后,我们想要运行一个任务来提醒管理员通知他们新注册并向新用户发送欢迎电子邮件。我们将通过分派一个名为 AlertNewUser
的队列任务来做到这一点,如下所示:
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); });
当您开始一个事务并对其中的任何数据进行更改时,这些更改仅对正在运行事务的请求/进程可用。对于任何其他访问您更改的数据的请求或进程,必须先提交事务。因此,这意味着如果我们从事务内部分派任何排队的队列、事件监听器、邮件,通知或广播事件。由于竞争条件,我们的数据更改可能在事务内部不可用。
如果队列在事务提交之前开始处理排队的代码,就会发生这种情况。因此,这可能导致您的排队代码可能试图访问不存在的数据,并可能导致错误。在我们的例子中,如果在事务提交之前运行队列AlertNewUser
作业,那么我们的作业将尝试访问一个尚未实际存储在数据库中的用户。如您所料,这将导致作业失败。
为了防止这种竞争条件的发生,我们可以对我们的代码和/或我们的配置进行一些更改,以确保仅在事务成功提交后才调度队列。
我们可以更新 config/queue.php
并添加 after commit
字段。让我们想象一下,我们正在使用 redis
队列驱动程序,所以我们可以这样更新配置:
<?php return [ // ... 'connections' => [ // ... 'redis' => [ 'driver' => 'redis', // ... 'after_commit' => true, ], // ... ], // ... ];
通过进行此更改,如果我们尝试在事务内调度队列,则队列将在实际调度队列之前等待事务提交。 方便的是,如果事务回滚,它也会阻止队列被调度。
然而,可能有一个原因,您不希望在配置中全局设置此选项。 如果是这种情况,Laravel 仍然提供了一些很好的助手方法,我们可以根据具体情况使用它们。
如果我们想更新事务中的代码,只在任务提交后才分派任务,可以使用afterCommit()
方法,如下所示:
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)->afterCommit(); });
Laravel 还提供了另一个我们可以使用的方便的beforeCommit()
方法。 如果我们在队列配置中设置了全局after_commit => true
,但不关心等待事务被提交,就可以使用这个。 要做到这一点,我们可以简单地像这样更新我们的代码:
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)->beforeCommit(); });
希望本文能让您大致了解什么是数据库事务以及如何在 Laravel 中使用它们。 它还向您展示了如何在从内部事务调度队列时避免“陷阱”。
原文地址:https://dev.to/ashallendesign/using-database-transactions-to-write-safer-laravel-code-13ek
译文地址:https://learnku.com/laravel/t/61575
【相关推荐:laravel视频教程】
Atas ialah kandungan terperinci Mari kita bincangkan tentang cara menggunakan transaksi pangkalan data dengan betul dalam kod Laravel. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!