深入理解 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()
将会确保我们使用了上面的配置。
在哪里使用
上面的代码我们可以在下面的几个地方使用:
- HTTP 请求
- 命令行
- 队列任务
让我们新建一个 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 协议》,转载必须注明作者和本文链接
请教大佬:最近在手写一个队列的时候遇到一个问题:
@悲剧不上演 这个不是太清楚了😂
使用
当
tenant1
里面的账号密码错误,或者禁止外网访问的ip链接如何捕获异常?(try{}catch (Exception $exception){} 无效,捕获不到异常)createPayloadUsing 5.5的版本 好像没有这个方法 能有其他办法解决 队列 切换数据库问题么?
大佬,有没有队列 中动态切换数据库的案例呢