文章转自专业的Laravel开发者社区,原文链接:https://learnku.com/laravel/t...对象关系映射(ORM)让处理数据变得异常简单。因为以面向对象的方式定义数据之间的关系,可以方便查询关联的模型数据,开发者无需关注底层的数据调用。ORM的标准数据优化是相关数据的预加载。我们将建立一些示例关系,然后介绍查询如何随急切和非急切加载而变化。我喜欢直接使用代码来进行实验,并通过一些示例来说明预加载的工作原理,这将进一步帮助您了解如何优化查询。简介在基本层面上,ORM在加载相关模型数据时是“懒惰的”。但是ORM应该如何知道您的意图呢?查询模型后,您可能永远不会真正使用相关模型的数据。不优化查询被称为“N+1”问题。当您使用对象来表示查询时,您可能在不知情的情况下进行查询。假设您从数据库中收到100个对象,每个记录都有1个关联模型(即belongsTo)。默认情况下,使用ORM将导致101个查询;一次查询原始100条记录,如果访问模型对象上的相关数据,则查询每条记录。在伪代码中,假设您要列出已发表作者的所有已发表文章。从一组帖子(每个帖子都有一个作者)中,您可以获得一个作者姓名列表,如下所示:$posts=Post::published()->get();//一个查询$authors=array_map(function($post){//为作者模型生成一个查询return$post->author->name;},$posts);我们没有告诉模型我们想要所有作者,所以我们每次都从每个Post模型实例中获取作者对每个名字进行单独的查询。预先加载正如我提到的,ORM是“惰性”加载关联。如果你打算使用关联的模型数据,你可以使用预加载将101个查询减少到2个查询。你只需要告诉模型你想要它加载什么。这是使用预加载的RailsActiveRecord指南中的示例。如您所见,这个概念与Laravel的预加载概念非常相似。#Railsposts=Post.includes(:author).limit(100)#Laravel$posts=Post::with('author')->limit(100)->get();通过从更广阔的角度探索,我发现我获得了更好的理解。ActiveRecord文档涵盖了一些示例,可以进一步帮助这个想法引起共鸣。Laravel的EloquentORMLLaravel的ORM,称为Eloquent,可以轻松预加载模型,甚至是嵌套关系模型。下面我们以Post模型为例,学习如何在Laravel项目中使用预加载。我们将构建这个项目,然后更深入地研究一些预加载的示例以结束。构建让我们构建一些数据库迁移、模型和数据库种子来试验预加载。如果你想继续,我假设你可以访问数据库并且可以完成基本的Laravel安装。使用Laravel安装程序,创建一个新项目:laravelnewblog-example根据您的数据库和选择编辑.env文件。接下来,我们将创建三个模型,以便您可以试验预加载嵌套关系。这个例子很简单,所以我们可以专注于预先加载,我省略了你可能会用到的东西,比如索引和外键约束。phpartisanmake:model-mPostphpartisanmake:model-mAuthorphpartisanmake:model-m配置文件-m标志创建迁移以与将用于创建表模式的模型一起使用。数据模型将具有以下关联:Post->belongsTo->AuthorAuthor->hasMany->PostAuthor->hasOne->Profile迁移让我们为每个数据表创建一个配置文件结构。我只添加了up()方法,因为Laravel会自动为新表添加down()方法。这些迁移文件放了数据库/迁移/目录中:increments('id');$table->unsignedInteger('author_id');$table->string('title');$table->text('body');$table->timestamps();});}/***回滚迁移**@returnvoid*/publicfunctiondown(){Schema::dropIfExists('posts');}}increments('id');$table->string('name');$table->text('bio');$table->时间戳();});}/***回滚迁移**@returnvoid*/publicfunctiondown(){Schema::dropIfExists('authors');}}increments('id');$table->unsignedInteger('author_id');$table->date('生日');$table->string('city');$table->string('state');$table->string('website');$table->timestamps();});}/***回滚迁移**@returnvoid*/publicfunctiondown(){Schema::dropIfExists('profiles');您需要定义模型关联并通过预先加载进行更多实验当您运行phpartisanmake:model命令时,它将为您创建模型文件。第一个模型是app/Post.php:belongsTo(Author::class);接下来,app\Author.php模型有两个关系:班级);}publicfunctionposts(){return$this->hasMany(Post::class);}}使用模型和迁移,您可以运行迁移并继续尝试预加载一些种子模型数据。phpartisanmigrateMigrationtablecreatedsuccessfully.Migrating:2014_10_12_000000_create_users_tableMigrated:2014_10_12_000000_create_users_tableMigrating:2014_10_12_100000_create_password_resets_tableMigrated:2014_10_12_100000_create_password_resets_tableMigrating:2017_08_04_042509_create_posts_tableMigrated:2017_08_04_042509_create_posts_tableMigrating:2017_08_04_042516_create_authors_tableMigrated:2017_08_04_042516_create_authors_tableMigrating:2017_08_04_044554_create_profiles_tableMigrated:2017_08_04_044554_create_profiles_table如果你查看下数据库,你就会看到所有已经创建好的数据表!FactoryModelsIn为了让我们能够运行查询,我们需要创建一些假数据来提供查询,让我们添加一些工厂模型并使用这些模型向数据库提供测试数据。打开database/factories/ModelFactory.php文件,在已有的User工厂模型文件中添加如下三个工厂模型:/**@var\Illuminate\Database\Eloquent\Factory$factory*/$factory->define(App\Post::class,function(Faker\Generator$faker){return['title'=>$faker->sentence,'author_id'=>function(){返回工厂(App\Author::class)->create()->id;},'body'=>$faker->paragraphs(rand(3,10),true),];});/**@var\Illuminate\Database\Eloquent\Factory$factory*/$factory->define(App\Author::class,function(Faker\Generator$faker){return['name'=>$faker->name,'bio'=>$faker->paragraph,];});$factory->define(App\Profile::class,function(Faker\Generator$faker){return['birthday'=>$faker->dateTimeBetween('-100years','-18years'),'author_id'=>function(){returnfactory(App\Author::class)->create()->id;},'city'=>$faker->city,'state'=>$faker->state,'website'=>$faker->domainName,];});这些工厂模型可以很容易地填充一些我们可以用来查询数据的模型;我们还可以使用它们来创建和生成关联模型所需的数据打开database/seeds/DatabaseSeeder.php文件并将以下内容添加到DatabaseSeeder::run()方法中:publicfunctionrun(){$authors=factory(App\Author::class,5)->create();$authors->each(函数($author){$author->profile()->save(factory(App\Profile::class)->make());$author->posts()->saveMany(factory(App\Post::class,rand(20,30))->make());});}你创建了五个作者并循环遍历每个作者,创建并保存与每个作者相关的配置文件和帖子(每个作者的帖子数量在20到30之间)。我们已经完成了迁移、模型、工厂模型和数据库填充的创建,它们可以组合起来以可重复的方式重新运行迁移和数据库填充:phpartisanmigrate:refreshphpartisandb:seed你现在应该有一些填充数据,你可以在下一章中使用它们。请注意,Laravel5.5包含一个migrate:fresh命令,它会删除表而不是回滚迁移并重新应用它们。尝试预加载现在我们的初步工作终于完成了。个人认为最好的可视化方式是将查询结果记录在storage/logs/laravel.log文件中以供查看。有两种方式可以将查询结果记录到日志中。第一个是打开MySQL日志文件,第二个是使用Eloquent数据库调用来实现。通过Eloquent记录查询语句,可以在app/Providers/AppServiceProvider.php的boot()方法中添加如下代码:namespaceApp\Providers;useDB;useLog;useIlluminate\Support\ServiceProvider;classAppServiceProviderextendsServiceProvider{/***引导任何应用程序服务。**@returnvoid*/publicfunctionboot(){DB::listen(function($query){Log::info($query->sql,$query->bindings,$query->time);});}//...}我喜欢在配置检查中封装这个监听器,这样我就可以控制记录查询日志的开关。您还可以从Laravel调试栏获得更多信息。首先,尝试不使用预加载模型时会发生什么。清除你的storage/log/laravel.log文件并运行“tinker”命令:phpartisantinker>>>$posts=App\Post::all();>>>$posts->map(function($post){...return$post->author;...});>>>...此时查看你的laravel.log文件,你会发现一堆查询语句查询作者:[2017-08-0406:21:58]local.INFO:select*from`posts`[2017-08-0406:22:06]local.INFO:select*from`authors`where`authors`.`id`=?limit1[1][2017-08-0406:22:06]local.INFO:select*from`authors`where`authors`.`id`=?limit1[1][2017-08-0406:22:06]local.INFO:从`authors`中选择*,其中`authors`.`id`=?limit1[1]....然后,再次清空laravel.log文件,这次使用with()方法通过预加载查询作者信息:phpartisantinker>>>$posts=App\Post::with('作者')->get();>>>$posts->map(function($post){...return$post->author;...});...这次你应该看到了,只有两条查询语句。一种是查询所有帖子以及与帖子关联的作者:[2017-08-0407:18:02]local.INFO:select*from`posts`[2017-08-0407:18:02]local。信息:select*from`authors`where`authors`.`id`in(?,?,?,?,?)[1,2,3,4,5]如果你有多个关联模型,你可以使用一个预加载数组:$posts=App\Post::with(['author','comments'])->get();Eloquent中的嵌套预加载来完成同样的工作。在我们的示例中,每个作者模型都有一个关联的简历。因此,我们将查询每个配置文件。清空laravel.log文件并尝试一下:phpartisantinker>>>$posts=App\Post::with('author')->get();>>>$posts->map(function($post){...return$post->author->profile;...});...你现在可以看到七个查询语句,前两个是预加载的结果。然后,每次我们得到一个新的个人资料,我们需要查询所有作者的个人资料。通过预先加载,我们可以避免嵌套在模型关联中的额外查询。最后一次清除laravel.log文件并运行此命令:>>>$posts=App\Post::with('author.profile')->get();>>>$posts->map(function($post){...return$post->author->profile;...});现在,有三个查询语句:[2017-08-0407:27:27]local.INFO:select*from`posts`[2017-08-0407:27:27]local.INFO:select*from`authors`where`authors`.`id`in(?,?,?,?,?)[1,2,3,4,5][2017-08-0407:27:27]local.INFO:选择*from`profiles`where`profiles`.`author_id`in(?,?,?,?,?)[1,2,3,4,5]惰性预加载你可能只需要收集相关的一些基本条件模型。在这种情况下,可以延迟调用相关数据的一些其他查询:phpartisantinker>>>$posts=App\Post::all();...>>>$posts->load('author.profile');>>>$posts->first()->author->profile;...在调用$posts->load()方法后,您应该只会看到三个查询。小结希望大家多了解预加载模型,更深层次地理解它是如何工作的。预加载文档非常全面,希望一些额外的代码实现可以帮助大家更好的优化关系查询。
