搜尋
首頁php框架Laravel一文詳解在Laravel使用資料庫事務的方法

引言

在 web 發展中,資料的完整性和準確性非常重要。因此,必須確保我們編寫的程式碼能夠以安全的方式儲存、更新和刪除資料庫中的資料。

在本文中,我們將看看什麼是資料庫事務,為什麼它們很重要,以及如何在 Laravel 開始使用它們。我們還將研究一個常見的“問題”,涉及隊列和資料庫事務。

什麼是資料庫事務?

在我們開始研究 Laravel 的資料庫事務之前,讓我們先看看它們是什麼以及它們如何有益。

對於什麼是資料庫事務,有許多聽起來複雜的技術解釋。但是,對於大多數 web 開發人員來說,我們只需要知道事務是完成資料庫中整個工作單元的方式。

為了理解這實際上意味著什麼,讓我們來看一個基本的例子,它將給出一點上下文。

假設我們有一個允許用戶註冊的應用程式。每當用戶註冊時,我們都希望為他們建立一個新帳戶,然後為他們分配一個預設角色「 general」。 【相關推薦:最新的五個Laravel影片教學

我們的程式碼可能是這樣的:

$user = User::create([
    'email' => $request->email,
]);

$user->roles()->attach(Role::where('name', 'general')->first());

乍一看,這段程式碼似乎完全沒問題。但是,當我們仔細觀察的時候,我們可以發現實際上有一些事情可能會出錯。我們可以創建用戶,但是不能為他們分配角色。這可能是由許多不同的原因造成的,例如分配角色的程式碼中的錯誤,甚至是阻止我們到達資料庫的硬體問題。

由於這種情況的發生,這將意味著系統中將有一個沒有角色的使用者。正如您可以想像的那樣,這可能會在您的應用程式中的其他地方引起異常和 bug,因為您總是假設用戶有一個角色(這是正確的)。

因此,為了解決這個問題,我們可以使用資料庫事務。透過使用事務,它可以確保在執行程式碼時,如果出現任何錯誤,事務內部對資料庫的任何變更都會回滾。例如,如果使用者被插入到資料庫中,但是由於任何原因分配角色的查詢失敗,那麼交易將被回滾,用戶行將被刪除。透過這樣做,它意味著我們不能創建沒有分配角色的使用者。

換句話說,它「要麼全有,要麼全沒有」。

在 Laravel 中使用資料庫事務

現在我們對事務是什麼以及它們實現了什麼有了一個簡單的概念,讓我們來看看如何在 Laravel 中使用它們。

在 Laravel 中,由於我們可以在 DB 門面上存取 transaction() 方法,因此開始使用事務實際上是很容易的事。繼續使用先前的範例程式碼,讓我們看看在建立使用者並為其指派角色時如何使用交易。

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());
});

現在我們的程式碼被包裹在一個資料庫事務中,如果在其中的任意一點拋出異常,對資料庫的任何更改都將返回到事務開始之前的狀態。

在 Laravel 中手動使用資料庫事務

有時,您可能希望對交易進行更精細的控制。例如,假設您正在與第三方服務集成,例如 Mailchinp 或 Xero。我們會說,每當您建立新使用者時,您還需要向他們的 API 發出 HTTP 請求,以將他們也建立為該系統中的使用者。

我們可能想要更新我們的程式碼,以便如果我們無法在我們自己的系統 在第三方系統中建立用戶,則兩個系統都不建立用戶。如果您正在與第三方系統交互,那麼您可能有一個可用於發出請求的類別。或者,可能有一個您可以使用的包。有時,當某些請求無法完成時,發出請求的類別可能會拋出異常。然而,其中一些類別可能會消除錯誤,而只是從您呼叫的方法中傳回 false,並將錯誤放置在類別的欄位中。

因此,我們假設我們有以下呼叫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;
    }
}

當然,上面的請求類別程式碼是不完整的,我下面的程式碼範例也不是很清楚,但它應該能讓您大致了解我要表達的觀點。所以讓我們使用這個請求類別並將其添加到我們之前的程式碼範例中:

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());

查看上面的代码,我们可以看到我们启动了一个事务,创建了用户并为他们分配了一个角色,然后我们调用了第三方服务。如果在外部服务中成功创建了用户,知道所有内容都已正确创建,我们就可以安全地提交数据库更改。但是,如果没有在外部服务中创建用户,则回滚数据库中的更改(删除用户及其角色分配),然后报告错误。

与第三方服务交互的技巧

作为一个额外的技巧,我通常建议将任何影响第三方系统、文件存储或缓存的代码放在数据库调用之后

为了更深入地理解这一点,让我们以上面的代码示例为例。请注意,在向第三方服务发出请求之前,我们是如何首先对数据库进行所有更改的。这意味着,如果从第三方请求返回任何错误,将回滚我们自己数据库中的用户和角色分配。

然而, 如果我们反过来做,我们在修改数据库之前发出请求,那就不是这种情况了。出于任何原因,如果我们在数据库中创建用户时发生任何错误,我们会在第三方系统中创建一个新用户,但是在我们系统中却没有创建。如你所想, 这可能会导致更多问题。通过编写一个清理方法将用户从第三方系统中删除,可以降低这个问题的严重性。 但是,正如您可以想象的那样, 这可能会导致更多的问题,并导致编写、维护和测试更多的代码。

所以,我总是建议把数据库调用放在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 [

    // ...

    &#39;connections&#39; => [

        // ...

        '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使用資料庫事務的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:learnku。如有侵權,請聯絡admin@php.cn刪除
laravel单点登录方法详解laravel单点登录方法详解Jun 15, 2022 am 11:45 AM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于单点登录的相关问题,单点登录是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,下面一起来看一下,希望对大家有帮助。

一起来聊聊Laravel的生命周期一起来聊聊Laravel的生命周期Apr 25, 2022 pm 12:04 PM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于Laravel的生命周期相关问题,Laravel 的生命周期从public\index.php开始,从public\index.php结束,希望对大家有帮助。

laravel中guard是什么laravel中guard是什么Jun 02, 2022 pm 05:54 PM

在laravel中,guard是一个用于用户认证的插件;guard的作用就是处理认证判断每一个请求,从数据库中读取数据和用户输入的对比,调用是否登录过或者允许通过的,并且Guard能非常灵活的构建一套自己的认证体系。

laravel中asset()方法怎么用laravel中asset()方法怎么用Jun 02, 2022 pm 04:55 PM

laravel中asset()方法的用法:1、用于引入静态文件,语法为“src="{{asset(‘需要引入的文件路径’)}}"”;2、用于给当前请求的scheme前端资源生成一个url,语法为“$url = asset('前端资源')”。

实例详解laravel使用中间件记录用户请求日志实例详解laravel使用中间件记录用户请求日志Apr 26, 2022 am 11:53 AM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于使用中间件记录用户请求日志的相关问题,包括了创建中间件、注册中间件、记录用户访问等等内容,下面一起来看一下,希望对大家有帮助。

laravel中间件基础详解laravel中间件基础详解May 18, 2022 am 11:46 AM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于中间件的相关问题,包括了什么是中间件、自定义中间件等等,中间件为过滤进入应用的 HTTP 请求提供了一套便利的机制,下面一起来看一下,希望对大家有帮助。

laravel路由文件在哪个目录里laravel路由文件在哪个目录里Apr 28, 2022 pm 01:07 PM

laravel路由文件在“routes”目录里。Laravel中所有的路由文件定义在routes目录下,它里面的内容会自动被框架加载;该目录下默认有四个路由文件用于给不同的入口使用:web.php、api.php、console.php等。

laravel的fill方法怎么用laravel的fill方法怎么用Jun 06, 2022 pm 03:33 PM

在laravel中,fill方法是一个给Eloquent实例赋值属性的方法,该方法可以理解为用于过滤前端传输过来的与模型中对应的多余字段;当调用该方法时,会先去检测当前Model的状态,根据fillable数组的设置,Model会处于不同的状态。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),