🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## **简介** Laravel 为我们提供了多种工具实现对数据库的增删改查,在我们使用 Laravel 提供的这些数据库工具之前,首先要连接到数据库。 ## **数据库连接配置** 数据库的连接配置文件位于`config/database.php`,默认情况下,Laravel为支持的每一种数据库都定义了一个连接配置项,你自行决定将哪个「连接」作为默认连接: ~~~ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, 'schema' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, ], ], ~~~ 这里包括了 SQLite、MySQL、PostgresSQL、SQL Server,一般我们默认使用的都是 MySQL: ~~~ 'default' => env('DB_CONNECTION', 'mysql'), ~~~ 当然默认数据库连接、数据库名称以及数据库用户名和密码等敏感信息都保存到`.env`文件中了,我们平时修改数据库连接信息的话修改这里就好了。 ### **配置多个数据连接** 有时候,我们的应用用到的不止一个数据库,或者做项目迁移的时候要做新老数据库之间的数据迁移,这个时候我们就可以配置多个数据库连接,如果我们的新老数据库使用的都是 MySQL 的话,可以在`config/database.php`的`connections`配置项中新增一个 MySQL 连接: ~~~ 'mysql_old' => [ 'driver' => 'mysql', 'host' => env('DB_HOST_OLD', '127.0.0.1'), 'port' => env('DB_PORT_OLD', '3306'), 'database' => env('DB_DATABASE_OLD', 'forge'), 'username' => env('DB_USERNAME_OLD', 'forge'), 'password' => env('DB_PASSWORD_OLD', ''), 'unix_socket' => env('DB_SOCKET_OLD', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ], ~~~ 然后在`.env`中新增对应配置项: ~~~ DB_CONNECTION_OLD=mysql DB_HOST_OLD=mysql DB_PORT_OLD=3306 DB_DATABASE_OLD=laravel56 DB_USERNAME_OLD=root DB_PASSWORD_OLD=root ~~~ 接下来,我们要怎样连上这个实例呢?默认情况下,我们通过Laravel提供的数据库工具(DB 门面、查询构建器、Eloquent模型)连接数据库的时候,都没有显式指定连接,因为我们在配置文件中指定了默认的连接因为我们在配置文件中指定了默认的连接`mysql`。所以要连接上其它连接很简单,在查询的时候指定这个新的连接就好了,如果你使用的是`DB`门面执行原生 SQL 查询,可以这么连接老的数据库: ~~~ $users = DB::connection('mysql_old')->select(...); DB::connection('mysql_old')->insert(...); ~~~ 如果你使用的 Eloquent 模型类,可以在对应模型类中设置`$connection`属性: ~~~ protected $connection = 'mysql_old'; ~~~ 这样,在模型类上执行查询、插入等操作时都会使用这个`mysql_old`数据库连接。 ### **配置数据库读写分离连接** 随着应用访问量的增长,对数据库进行读写分离可以有效的提升应用整体性能,Laravel也对此进行了单独支持(仅讨论从应用层面如何在 Laravel 项目中配置读写分离连接)。Laravel 框架数据库底层代码对数据库读写分离进行了支持,所以我们需要遵循底层实现进行读写分离配置: ~~~ 'mysql' => [ 'driver' => 'mysql', 'read' => [ 'host' => env('DB_HOST_READ', '127.0.0.1'), //当读写数据库的用户名、密码等不一样时,将username、password等对应字 段移至此处即可 ], 'write' => [ 'host' => env('DB_HOST_WRITE', '127.0.0.1'), ], 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ], ~~~ `read`项配置的是「读」连接,`write`项配置的是「写」连接。然后在`.env`中新增`DB_HOST_READ`和`DB_HOST_WRITE`配置项。当然,对于 Web 应用而言,大多是读多写少,所以你还可以配置多个`read`主机,Laravel 底层的负载均衡机制是随机从配置的 IP 中挑一个连接: ~~~ 'read' => [ 'host' => [env('DB_HOST_READ_1'), env('DB_HOST_READ_2')], ], ~~~ 针对读写分离数据库的连接,Laravel 数据库底层会自动判断,如果是查询语句会使用读连接,如果是数据库插入、更新、删除等操作会使用写连接。 #### **读写分离配置中的`sticky`配置项** 在读写分离配置中,我们注意到新增了一个`sticky`配置项,这个是用来干嘛的呢? 我们配置数据库读写分离的时候,会配置读数据库(从库)从写数据库(主库)同步数据,由于不同主机之间数据同步是需要时间的,虽然这个时间很短,但是对于并发量很大的应用,还是可能出现写入写数据库的数据不能立即从读数据库读取到的情况,`sticky`配置项在这个时候就派上用场了。如果该配置项设置为`true`的话,在同一个请求生命周期中,写入的数据会被立刻读取到,底层原理其实就是读操作也从写数据库读取,因为写数据库始终是最新数据,从而避免主从同步延迟导致的数据不一致。 ## **通过迁移文件定义数据表结构** 在诸如 Laravel 这种现代框架中,数据表的每次变动(创建、修改、删除)都对应一个迁移文件。这些迁移文件位于`database/migrations`目录下,以日期时间为条件确定执行的先后顺序。每个迁移文件中包含一个迁移类,这个迁移类有两部分组成:负责执行数据库迁移的`up`方法,以及负责回滚此次迁移的`down`方法。以 Laravel 自带的`users`表迁移文件为例,代码如下所示: ![](https://img.kancloud.cn/c2/a0/c2a0f10c280fde0565e5577efffccac3_2492x1234.jpg) 当我们迁移数据库时,系统获取所有数据库迁移文件(包括`database/migrations`目录下和扩展包中注册的),然后按照文件名中包含的日期时间排序,从最早的迁移文件开始,依次执行每个迁移类中的`up`方法,最后完成数据库迁移;反之,当我们回滚数据库时,按照日期时间排序,从最晚的迁移文件开始,依次执行每个迁移类的`down`方法,最后完成数据库回滚,如果指定回滚其中某几步的话,回滚到对应的迁移文件即终止。 ### **创建迁移文件** Laravel 提供了一个 Artisan 命令`make:migration`帮助我们快速生成数据库迁移文件,该命名包含一个参数,就是要创建的迁移的名称,比如要创建`users`表对应迁移文件,可以通过`php artisan make:migration create_users_table`命令来完成。 此外,这个 Artisan 命令还支持两个可选的选项,`--create=`用于指定要创建的数据表名称,以及`--table=`用于指定要修改的数据表名称,前者在定义创建数据表迁移文件时使用,后者在定义更新数据表迁移文件时使用,比如我们还是以`users`表为例: ~~~ php artisan make:migration create_users_table --create=users #创建数据表迁移 php artisan make:migration alter_users_add_nickname --table=users # 更新数据表迁移 ~~~ 有了迁移文件后,就可以在迁移文件对应迁移类的`up`方法中编写创建数据表的逻辑了,以`create_users_table`迁移为例: ~~~ public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } ~~~ 我们对数据库的迁移操作都是基于`Schema`门面来完成(底层对应的类是`Illuminate\Database\Schema\Builder`),比如创建数据表,需要调用该门面的`create`方法,该方法的第一个参数是要创建的数据表的名称,第二个参数是一个闭包,其中定义的是新增数据表的所有字段信息。 ### **运行迁移** 接下来,我们来运行上面定义的迁移文件执行数据库变更,常见的操作有两种:执行变更和回滚变更。 执行变更很简单,通过`php artisan migrate `就可以按照迁移文件生成时间的先后顺序依次执行所有的数据库迁移。 回滚要稍微复杂点,Laravel 支持多种形式的回滚,如果只回滚最后一个迁移文件的变更,可以通过:`php artisan migrate:rollback`来实现,如果要回滚多个迁移文件的变更,可以通过`--step=`指定步数(按照迁移文件生成时间逆序执行): ~~~ php artisan migrate:rollback --step=5 ~~~ 如果是要回滚所有迁移文件的变更,将数据库恢复到初始状态,需要运行以下命令: ~~~ php artisan migrate:reset ~~~ ## **通过填充器快速填充测试数据** 在Laravel框架中,我们可以借助其提供的填充器功能非常方便地为不同数据表快速填充测试数据。 ### **填充器的运行** 在应用根目录的`database/seeds`目录下,默认包含一个`DatabaseSeeder.php`文件。这就是 Laravel 自带的一个填充器示例文件,该填充器类提供了一个`run`方法,当我们运行填充命令时,就会调用该方法执行数据库填充。 Laravel 提供了两种方式来运行填充器: - 独立的填充命令: ~~~ //以`DatabaseSeeder`为入口类,调用该类的`run`方法,你可以将所有对其他填充器的调用定义在该方法中 php artisan db:seed //通过`--class=`选项指定运行某个填充器类的`run`方法 php artisan db:seed --class=UsersTableSeeder ~~~ - 在运行迁移命令时通过指定标识选项在创建数据表时填充(尤其是在初始化一些演示项目的时候): ~~~ //执行迁移命令时运行填充器类`DatabaseSeeder`填充数据 php artisan migrate --seed //回滚所有迁移并重新运行迁移同时填充初始化数据 php artisan migrate:refresh --seed ~~~ ### **编写填充器类** 知道了如何运行填充器,是时候来编写第一个填充器类了。我们可以通过如下 Artisan 命令为`users`表快速创建一个填充器类`UsersTableSeeder`: ~~~ php artisan make:seeder UsersTableSeeder ~~~ 该命令会在`database/seeds`目录下创建一个`UsersTableSeeder`填充器类, 我们可以将填充逻辑编写到`run`方法里: ~~~ public function run() { DB::table('users')->insert([ 'name' => str_random(10), 'email' => str_random(10).'@gmail.com', 'password' => bcrypt('secret'), ]); } ~~~ 这里我们借助了查询构建器来插入数据,指定用户名和邮箱为长度不大于10的随机字符串,邮箱后缀是`@gmail.com`,密码是对`secret`字符串进行加密后的字符串。接下来,我们可以通过指定填充器类的方式将这条记录插入到数据库: ~~~ php artisan db:seed --class=UsersTableSeeder ~~~ 当然,你还可以在`DatabaseSeeder`类的`run`方法中运行这个填充器类: ~~~ public function run() { $this->call(UsersTableSeeder::class); } ~~~ 如果有多个填充器类,想要一次性运行,可以将它们都放到这个方法中调用。 ### **通过模型工厂填充数据** 以上编写填充器类填充数据到数据库虽然已经很方便了,但是每次插入一条记录都要编写一条语句或者手动指定插入数据,如果需要填充的测试数据有成千上万条,那不是要崩溃掉。有没有一种机制可以一次定义填充规则,在每次具体运行时自行决定填充多少条记录呢?模型工厂的概念应运而生:我们在一个 Eloquent 模型类(后续学习)上定义一个工厂方法,通过指定规则批量插入填充数据。 模型工厂位于`database/factories`目录下,Laravel 自带了一个用于填充`User`模型的模型工厂`UserFactory.php`: ~~~ <?php use Faker\Generator as Faker; /* |-------------------------------------------------------------------------- | Model Factories |-------------------------------------------------------------------------- | | This directory should contain each of the model factory definitions for | your application. Factories provide a convenient way to generate new | model instances for testing / seeding your application's database. | */ $factory->define(App\User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 'remember_token' => str_random(10), ]; }); ~~~ 在模型工厂文件中,我们通过`$factory->define`方法来定义`User`模型的模型工厂,该方法的第一个参数是模型类,第二个参数是一个匿名函数,在该匿名函数中我们通过[Faker](https://github.com/fzaninotto/Faker)类库提供的方法来定义字段规则,Faker 类库提供了丰富的字段规则帮助我们生成伪造字段值,这些规则可以在[官方文档](https://github.com/fzaninotto/Faker)中查看,这里,我们使用`$faker->name`生成用户名,`$faker->unique()->safeEmail`生成唯一的邮箱地址,最后再将这些字段模型返回。 #### **调用模型工厂** 在调用这些模型工厂的时候,需要借助 Laravel 提供的全局辅助函数`factory()`,比如我们在`UsersTableSeeder`的`run`方法中通过模型工厂改写数据填充方法: ~~~ public function run() { /*DB::table('users')->insert([ 'name' => str_random(10), 'email' => str_random(10).'@gmail.com', 'password' => bcrypt('secret'), ]);*/ factory(\App\User::class, 5)->create(); } ~~~ 由于我们在`UserFactory.php`中全局定义了`User`模型的模型工厂,所以在这里只需调用`factory(...)->create()`方法,传入对应模型类和要填充的记录数即可,当然,最后还是通过运行`php artisan db:seed`命令来填充数据到数据库。