目录
与第三方服务交互的技巧" >与第三方服务交互的技巧
使用自动或手动事务" >使用自动或手动事务
在数据库事务中调度队列" >在数据库事务中调度队列
首页 php框架 Laravel 聊聊Laravel代码中怎么正确地使用数据库事务

聊聊Laravel代码中怎么正确地使用数据库事务

Sep 30, 2022 pm 05:45 PM
php laravel

聊聊Laravel代码中怎么正确地使用数据库事务

在 web 开发中,数据的完整性和准确性非常重要。因此,必须确保我们编写的代码能够以安全的方式存储、更新和删除数据库中的数据。

在本文中,我们将看看什么是数据库事务,为什么它们很重要,以及如何在 Laravel 开始使用它们。我们还将研究一个常见的“问题”,涉及队列和数据库事务。

什么是数据库事务?

在我们开始研究 Laravel 的数据库事务之前,让我们先看看它们是什么以及它们如何有益。

对于什么是数据库事务,有许多听起来复杂的技术解释。但是,对于大多数 web 开发人员来说,我们只需要知道事务是完成数据库中整个工作单元的方式。

为了理解这实际上意味着什么,让我们来看一个基本的例子,它将给出一点上下文。

假设我们有一个允许用户注册的应用程序。每当用户注册时,我们都希望为他们创建一个新帐户,然后为他们分配一个默认角色“ general”。

我们的代码可能是这样的:

$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; => [

        // ...

        &#39;redis&#39; => [
            &#39;driver&#39; => &#39;redis&#39;,
            // ...
            &#39;after_commit&#39; => 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([
        &#39;email&#39; => $request->email,
    ]);

    $user->roles()->attach(Role::where(&#39;name&#39;, &#39;general&#39;)->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([
        &#39;email&#39; => $request->email,
    ]);

    $user->roles()->attach(Role::where(&#39;name&#39;, &#39;general&#39;)->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视频教程

以上是聊聊Laravel代码中怎么正确地使用数据库事务的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
如果session_start()被多次调用会发生什么? 如果session_start()被多次调用会发生什么? Apr 25, 2025 am 12:06 AM

多次调用session_start()会导致警告信息和可能的数据覆盖。1)PHP会发出警告,提示session已启动。2)可能导致session数据意外覆盖。3)使用session_status()检查session状态,避免重复调用。

作曲家:通过AI的帮助开发PHP 作曲家:通过AI的帮助开发PHP Apr 29, 2025 am 12:27 AM

AI可以帮助优化Composer的使用,具体方法包括:1.依赖管理优化:AI分析依赖关系,建议最佳版本组合,减少冲突。2.自动化代码生成:AI生成符合最佳实践的composer.json文件。3.代码质量提升:AI检测潜在问题,提供优化建议,提高代码质量。这些方法通过机器学习和自然语言处理技术实现,帮助开发者提高效率和代码质量。

session_start()函数的意义是什么? session_start()函数的意义是什么? May 03, 2025 am 12:18 AM

session_start()iscucialinphpformanagingusersessions.1)ItInitiateSanewsessionifnoneexists,2)resumesanexistingsessions,and3)setsasesessionCookieforContinuityActinuityAccontinuityAcconActInityAcconActInityAcconAccRequests,EnablingApplicationsApplicationsLikeUseAppericationLikeUseAthenticationalticationaltication and PersersonalizedContentent。

php框架laravel和yii区别是什么 php框架laravel和yii区别是什么 Apr 30, 2025 pm 02:24 PM

Laravel和Yii的主要区别在于设计理念、功能特性和使用场景。1.Laravel注重开发的简洁和愉悦,提供丰富的功能如EloquentORM和Artisan工具,适合快速开发和初学者。2.Yii强调性能和效率,适用于高负载应用,提供高效的ActiveRecord和缓存系统,但学习曲线较陡。

哪些数据库版本与最新的Laravel兼容? 哪些数据库版本与最新的Laravel兼容? Apr 25, 2025 am 12:25 AM

最新版本的Laravel10与MySQL5.7及以上、PostgreSQL9.6及以上、SQLite3.8.8及以上、SQLServer2017及以上兼容。这些版本选择是因为它们支持Laravel的ORM功能,如MySQL5.7的JSON数据类型,提升了查询和存储效率。

H5:HTML5的关键改进 H5:HTML5的关键改进 Apr 28, 2025 am 12:26 AM

HTML5带来了五个关键改进:1.语义化标签提升了代码清晰度和SEO效果;2.多媒体支持简化了视频和音频嵌入;3.表单增强简化了验证;4.离线与本地存储提高了用户体验;5.画布与图形功能增强了网页的可视化效果。

如何使用MySQL的函数进行数据处理和计算 如何使用MySQL的函数进行数据处理和计算 Apr 29, 2025 pm 04:21 PM

MySQL函数可用于数据处理和计算。1.基本用法包括字符串处理、日期计算和数学运算。2.高级用法涉及结合多个函数实现复杂操作。3.性能优化需避免在WHERE子句中使用函数,并使用GROUPBY和临时表。

Laravel 最佳扩展包推荐:2024 年必备工具 Laravel 最佳扩展包推荐:2024 年必备工具 Apr 30, 2025 pm 02:18 PM

2024年必备的Laravel扩展包包括:1.LaravelDebugbar,用于监控和调试代码;2.LaravelTelescope,提供详细的应用监控;3.LaravelHorizon,管理Redis队列任务。这些扩展包能提升开发效率和应用性能。

See all articles