説明: この記事では主に Laravel Eloquent の遅延プリロード (Eager Loading) について説明します。 、遅延プリロードを使用して MySQL クエリの数を減らします。同時に、作成者は開発プロセス中にいくつかのスクリーンショットとコードを貼り付けて、読み取り効率を向上させます。
注: 現在、4 つのテーブルがあります: マーチャント テーブル マーチャント、マーチャント電話 テーブル電話、マーチャント所有ショップ ショップ テーブル、およびショップ テーブル製品の製品。
[ 'merchants_phones' => 'one-to-one', 'merchants_shops' => 'one-to-many', 'shops_products' => 'one-to-many', ]
各店舗を一覧表示するページを作成する必要があります 各店舗ブロックには、タイトルなどの店舗情報、名前や電話番号などの店舗加盟店情報、所有する店舗情報が含まれます紹介や価格などの製品情報。プリロードの有無でどのような違いが生じるかを確認してください。
開発環境:Laravel5.1 MAMP PHP7 MySQL5.5
まずはストア一覧ページを書く
1. 開発を最初にインストールする 3 -ピース プラグイン セット
(詳細については、「Laravel Learning Notes: SeederFilling Data Tips」を参照してください)
何はともあれ、最初に 3 ピースの開発プラグイン セットをインストールしてください:
composer require barryvdh/laravel-debugbar --dev composer require barryvdh/laravel-ide-helper --dev composer require mpociot/laravel-test-factory-helper --dev //config/app.php /** *Develop Plugin */ Barryvdh\Debugbar\ServiceProvider::class, Mpociot\LaravelTestFactoryHelper\TestFactoryHelperServiceProvider::class, Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
2. テーブル フィールド、テーブルの関連付け、およびテスト データ フィラー Seeder
を記述し、順番に命令を入力します:
php artisan make:model Merchant -m php artisan make:model Phone -m php artisan make:model Shop -m php artisan make:model Product -m
テーブル フィールドとテーブルの関連付けを記述します:
class CreateMerchantsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('merchants', function (Blueprint $table) { $table->increments('id'); $table->string('username')->unique(); $table->string('email')->unique(); $table->string('first_name'); $table->string('last_name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('merchants'); } } class CreatePhonesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('phones', function (Blueprint $table) { $table->increments('id'); $table->integer('number')->unsigned(); $table->integer('merchant_id')->unsigned(); $table->timestamps(); $table->foreign('merchant_id') ->references('id') ->on('merchants') ->onUpdate('cascade') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('phones', function($table){ $table->dropForeign('merchant_id'); // Drop foreign key 'user_id' from 'posts' table }); Schema::drop('phones'); } } class CreateShopsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('shops', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('slug')->unique(); $table->string('site'); $table->integer('merchant_id')->unsigned(); $table->timestamps(); $table->foreign('merchant_id') ->references('id') ->on('merchants') ->onUpdate('cascade') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('shops', function($table){ $table->dropForeign('merchant_id'); // Drop foreign key 'user_id' from 'posts' table }); Schema::drop('shops'); } } class CreateProductsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->text('short_desc'); $table->text('long_desc'); $table->double('price'); $table->integer('shop_id')->unsigned(); $table->timestamps(); $table->foreign('shop_id') ->references('id') ->on('shops') ->onUpdate('cascade') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('products', function($table){ $table->dropForeign('shop_id'); // Drop foreign key 'user_id' from 'posts' table }); Schema::drop('products'); } } /** * App\Merchant * * @property integer $id * @property string $username * @property string $email * @property string $first_name * @property string $last_name * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property-read \App\Phone $phone * @property-read \Illuminate\Database\Eloquent\Collection|\App\Shop[] $shops * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereId($value) * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereUsername($value) * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereEmail($value) * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereFirstName($value) * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereLastName($value) * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereCreatedAt($value) * @method static \Illuminate\Database\Query\Builder|\App\Merchant whereUpdatedAt($value) * @mixin \Eloquent */ class Merchant extends Model { /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function phone() { return $this->hasOne(Phone::class, 'merchant_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function shops() { return $this->hasMany(Shop::class, 'merchant_id'); } } /** * App\Phone * * @property integer $id * @property integer $number * @property integer $merchant_id * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property-read \App\Merchant $merchant * @method static \Illuminate\Database\Query\Builder|\App\Phone whereId($value) * @method static \Illuminate\Database\Query\Builder|\App\Phone whereNumber($value) * @method static \Illuminate\Database\Query\Builder|\App\Phone whereMerchantId($value) * @method static \Illuminate\Database\Query\Builder|\App\Phone whereCreatedAt($value) * @method static \Illuminate\Database\Query\Builder|\App\Phone whereUpdatedAt($value) * @mixin \Eloquent */ class Phone extends Model { /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function merchant() { return $this->belongsTo(Merchant::class, 'merchant_id'); } } /** * App\Product * * @property integer $id * @property string $name * @property string $short_desc * @property string $long_desc * @property float $price * @property integer $shop_id * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property-read \Illuminate\Database\Eloquent\Collection|\App\Shop[] $shop * @method static \Illuminate\Database\Query\Builder|\App\Product whereId($value) * @method static \Illuminate\Database\Query\Builder|\App\Product whereName($value) * @method static \Illuminate\Database\Query\Builder|\App\Product whereShortDesc($value) * @method static \Illuminate\Database\Query\Builder|\App\Product whereLongDesc($value) * @method static \Illuminate\Database\Query\Builder|\App\Product wherePrice($value) * @method static \Illuminate\Database\Query\Builder|\App\Product whereShopId($value) * @method static \Illuminate\Database\Query\Builder|\App\Product whereCreatedAt($value) * @method static \Illuminate\Database\Query\Builder|\App\Product whereUpdatedAt($value) * @mixin \Eloquent */ class Product extends Model { /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function shop() { return $this->belongsTo(Shop::class, 'shop_id'); } } /** * App\Shop * * @property integer $id * @property string $name * @property string $slug * @property string $site * @property integer $merchant_id * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property-read \Illuminate\Database\Eloquent\Collection|\App\Merchant[] $merchant * @property-read \Illuminate\Database\Eloquent\Collection|\App\Product[] $products * @method static \Illuminate\Database\Query\Builder|\App\Shop whereId($value) * @method static \Illuminate\Database\Query\Builder|\App\Shop whereName($value) * @method static \Illuminate\Database\Query\Builder|\App\Shop whereSlug($value) * @method static \Illuminate\Database\Query\Builder|\App\Shop whereSite($value) * @method static \Illuminate\Database\Query\Builder|\App\Shop whereMerchantId($value) * @method static \Illuminate\Database\Query\Builder|\App\Shop whereCreatedAt($value) * @method static \Illuminate\Database\Query\Builder|\App\Shop whereUpdatedAt($value) * @mixin \Eloquent */ class Shop extends Model { /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function merchant() { return $this->belongsTo(Merchant::class, 'merchant_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function products() { return $this->hasMany(Product::class, 'shop_id'); } }
忘れないでください 3 つの入力命令セットを開発するには:
php artisan ide-helper:generate php artisan ide-helper:models php artisan test-factory-helper:generate
テーブル間の関係は次の図に示すとおりです:
次に、Seeder を作成します。Laravel の学習ノートを参照してください。Seeder にデータを入力するためのヒント:
php artisan make:seeder MerchantTableSeeder php artisan make:seeder PhoneTableSeeder php artisan make:seeder ShopTableSeeder php artisan make:seeder ProductTableSeeder class MerchantTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); $datas = []; foreach (range(1, 20) as $key => $value) { $datas[] = [ 'username' => $faker->userName , 'email' => $faker->safeEmail , 'first_name' => $faker->firstName , 'last_name' => $faker->lastName , 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() ]; } DB::table('merchants')->insert($datas); } } class PhoneTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); $merchant_ids = \App\Merchant::lists('id')->toArray(); $datas = []; foreach (range(1, 20) as $key => $value) { $datas[] = [ 'number' => $faker->randomNumber() , 'merchant_id' => $faker->randomElement($merchant_ids) , 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() ]; } DB::table('phones')->insert($datas); } } class ShopTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); $merchant_ids = \App\Merchant::lists('id')->toArray(); $datas = []; foreach (range(1, 40) as $key => $value) { $datas[] = [ 'name' => $faker->name , 'slug' => $faker->slug , 'site' => $faker->word , 'merchant_id' => $faker->randomElement($merchant_ids) , 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() ]; } DB::table('shops')->insert($datas); } } class ProductTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = Faker\Factory::create(); $shop_ids = \App\Shop::lists('id')->toArray(); $datas = []; foreach (range(1, 30) as $key => $value) { $datas[] = [ 'name' => $faker->name , 'short_desc' => $faker->text , 'long_desc' => $faker->text , 'price' => $faker->randomFloat() , 'shop_id' => $faker->randomElement($shop_ids) , 'created_at' => \Carbon\Carbon::now()->toDateTimeString() , 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() ]; } DB::table('products')->insert($datas); } } php artisan db:seed
3. 単純な View ビューを作成します
(1) リポジトリ パターンを使用してコードを整理します
##
//app/Repository namespace App\Repository; interface ShopRepositoryInterface { public function all(); } //app/Repository/Eloquent namespace App\Repository\Eloquent; use App\Repository\ShopRepositoryInterface; use App\Shop; class ShopRepository implements ShopRepositoryInterface { /** * @var Shop */ public $shop; public function __construct(Shop $shop) { $this->shop = $shop; } public function all() { // TODO: Implement all() method. $shops = $this->shop->all(); return $shops; } } //app/provider/ShopRepositoryServiceProvider //php artisan make:provider ShopRepositoryServiceProvider /** * Register the application services. * * @return void */ public function register() { $this->app->bind(ShopRepositoryInterface::class, ShopRepository::class); } //app/Http/Controllers/ShopController.php class ShopController extends Controller { /** * @var ShopRepositoryInterface */ public $shop; /** * ShopController constructor. * @param ShopRepositoryInterface $shopRepositoryInterface */ public function __construct(ShopRepositoryInterface $shopRepositoryInterface) { $this->shop = $shopRepositoryInterface; } public function all() { $shops = $this->shop->all(); return view('shop.index', compact('shops')); } } //视图 //resources/views/shop/layout.blade.php <meta> <meta> <meta> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>Bootstrap Template</title> <!-- 新 Bootstrap 核心 CSS 文件 --> <link> <style> html,body{ width: 100%; height: 100%; } *{ margin: 0; border: 0; } </style> <p> </p><p> </p><p> @yield('content') </p> <!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> <script></script> <!-- 最新的 Bootstrap 核心 JavaScript 文件 --> <script></script> <script> </script> //resources/views/shop/index.blade.php @extends('shop.layout') @section('content')
-
@foreach($shops as $shop)
-
Store:{{$shop->name}}
Member:{{$shop->merchant->first_name.' '.$shop->merchant->last_name}} {{--这里数组取电话号码--}} Phone:{{$shop->merchant->phone['number']}}-
@foreach($shop->products as $product)
-
Name:{{$product->name}}
Desc:{{$product->short_desc}}
Price:{{$product->price}}
{{-- {!! Debugbar::info('products:'.$product->id) !!}--}}
@endforeach
@endforeach
-
(2) デバッグバー プログラム実行データを表示
#121 個のクエリが実行され、38.89 ミリ秒かかり、効率が非常に低いことがわかります。各クエリを注意深く観察してください。1 つのステートメントでは、これが最初にショップ テーブルをスキャンし、次に、商品テーブルの検索も同様で、クエリが多く、N 1 検索の問題です。
クエリのプリロード
(1) ネストされたプリロードEloquent は、属性を通じて関連データにアクセスする場合、
遅延読み込み
になります。関連データは、プロパティを通じてアクセスするときにロードされます。上位層モデルを検索するときに関連データをプリロードすることで、N 1 問題を回避できます。 また、プリロードの使用は非常に簡単です。
変更する必要があるのは 1 行だけです:
//app/Repository/Eloquent/ShopRepository public function all() { // TODO: Implement all() method. // $shops = $this->shop->all(); //通过`点`语法嵌套预加载,多种关联就写对应的关联方法 //Shop这个Model里关联方法是Merchant()和Products(),Merchant Model里关联方法是Phone() $shops = $this->shop->with(['merchant.phone', 'products'])->get(); return $shops; }
他のコードを変更する必要はありません。デバッグバーのクエリを確認してください:
# #動作しています! !!
見つかった: クエリは 4 つだけで、所要時間は 3.58 ミリ秒で、効率が大幅に向上しました。元の N 1 クエリは
where..in..
(2) プリロードの条件制限
製品の事前並べ替えなど、プリロードに条件付き制限を課すこともできます。コードの変更は簡単です。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">//app/Repository/Eloquent/ShopRepository
public function all()
{
// TODO: Implement all() method.
// $shops = $this->shop->all();
// $shops = $this->shop->with(['merchant.phone', 'products'])->get();
$shops = $this->shop->with(['members.phone', 'products'=>function($query){
// $query->orderBy('price', 'desc');
$query->orderBy('price', 'asc');
}])->get();
return $shops;
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
制限条件を追加することは、商品のプリロード時に SQL ステートメントにソートを追加することと同等です。もうスクリーンショットは必要ありません。
要約: 関連モデルのプリロードは確かに興味深い機能であり、効率が大幅に向上します。最近ランダムに調べているんですが、何か面白いことがあったらシェアしますね。