深入理解 Laravel 中的数据库连接

本文为 Diving Laravel 的译文。 在这篇文章中我们将会探索 Laravel 中的多数据库连接。

在大多数的程序中只需要对一个数据库的连接进行处理。但是,也有一部分的 Laravel 应用需要处理多个数据库的连接。虽然有一些第三方的 library 能够为我们处理这些问题,但是我们如果能够理解 Laravel 是怎样处理数据库的连接对我们会很有帮助,下面让我们一起研究一下吧。

与数据库建立一个连接

当你在 Laravel 中进行查询时, Illuminate\Database\DatabaseManager 负责数据库的连接配置。每个连接都有一个独特的名称,并且你可以选择一个默认的配置,如果没有提供连接配置的名称:

// 使用默认的配置进行连接
DB::table('users')->all();

// 使用 "tenant" 配置进行连接
DB::connection('tenant')->table('users')->all();

在 Laravel 一次请求的生命周期内,连接只会建立一次,之后就会复用这个连接。

PDO

在 PHP 中 PDO是一个标准的连接数据库的接口, Laravel 使用 PDO 进行各种各样的查询。 当然,你可以配置读写分离的 PDO 对象,关于配置的详细介绍可以查看 Laravel 官方文档

在大多数的多租户应用中(multi-tenancy apps)都有一个分开的数据表来存储所有的租户信息。简单来讲,假设我们有一个主连接和租户连接,具体如下:

'tenant' => [
  'driver' => 'mysql',
  'host' => env('DB_HOST', '127.0.0.1'),
  'port' => env('DB_PORT', '3306'),
  // ...
],

'system' => [
  'driver' => 'mysql',
  'host' => env('DB_HOST', '127.0.0.1'),
  'port' => env('DB_PORT', '3306'),
  // ...
],

system 连接一直都使用同一个连接,那么查询对应的租户很简单:

DB::connection('system')->table('tenants')->all();

当我们想要查询指定租户的数据时,这时就变得有趣了起来。因为每个租户的数据库连接设定都不太相同,因此,我们不能简单的将所有的租户数据库配置写到 config/database.php 中。相反,我们在程序执行中进行配置。

config(['database.connections.tenant.database' => 'tenant1']);

上面的代码将读取 tenant1 中的设置并进行配置,你可以根据同样的方法来修改数据库的 username,password 等相关信息。

当 DatabaseManager 想要建立 tenant 连接时,就会使用我们上面配置好的连接。 然而,如果租户的连接已经建立,那么对配置文件的更改不会生效,因为在连接第一次建立的时候 Laravel 会对其进行缓存而不会重新创建一个新的实例。

要解决这个问题,你需要确保在使用我们的连接之前清空之前的缓存

config(['database.connections.tenant.database' => 'tenant1']);

DB::purge('tenant');

DB::reconnect('tenant');

使用 purge()reconnect() 将会确保我们使用了上面的配置。

在哪里使用

上面的代码我们可以在下面的几个地方使用:

  1. HTTP 请求
  2. 命令行
  3. 队列任务

让我们新建一个 TenancyProvider 并且将其加入到 config/app.php 中确保其生效, 我们可以用下面的方法进行配置:

public function register()
{
    if($this->app->runningInConsole())
     {
            return;
    }

    if($request->getHttpHost() == 'tenant1.app.com')
    {
            config(['database.connections.tenant.database' => 'tenant1']);

            DB::purge('tenant');

            DB::reconnect('tenant');
     }
}

通过上面的代码我们可以根据请求的域名来使用不同的数据库配置。至于队列任务,我们可以使用 tenant_id 来进行判断,代码如下:

$this->app['queue']->createPayloadUsing(function () {
      return Tenant::get() ? [
              'tenant_id' => Tenant::get()->id
             ] : [];
});

其中,Tenant::get() 里面是判断当前应当使用哪个配置的逻辑。现在每个 任务将会包含 telant_id 然后我们可以监听 JobProcessing 事件然后进行数据库配置

$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function($event){
    if (isset($event->job->payload()['tenant_id'])) {
        Tenant::set($event->job->payload()['tenant_id']);
    }
});
本作品采用《CC 协议》,转载必须注明作者和本文链接
There's nothing wrong with having a little fun.
Epona
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

请教大佬:最近在手写一个队列的时候遇到一个问题:

当队列中无数据的时候,laravel 是如何来解决 Mysql 自动断开连接的呢?

4年前 评论
Epona

@悲剧不上演 这个不是太清楚了😂

4年前 评论

使用

config(['database.connections.tenant.database' => 'tenant1']);

DB::purge('tenant');

tenant1 里面的账号密码错误,或者禁止外网访问的ip链接如何捕获异常?(try{}catch (Exception $exception){} 无效,捕获不到异常)

file

现在的需求是账号密码是用户输入的无法判断正确性

4年前 评论

createPayloadUsing 5.5的版本 好像没有这个方法 能有其他办法解决 队列 切换数据库问题么?

4年前 评论
Epona (楼主) 4年前

大佬,有没有队列 中动态切换数据库的案例呢

1年前 评论
悲剧不上演 1年前
Mr_Choi (作者) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!