Laravel Eloquent ORM 关系查询
在信息系统中有很多一对多的关系,常见的一个例子是一个订单(order)中包含多种商品(product)。我们通常会使用一个order表来存储订单,同时使用一个product表来存储商品,并在product表中加入order_id
外键来将产品关联到订单中。
在以前开发项目时,我一直使用先查询order,再通过order_id
查询product,并遍历查询到的order对象,将product插入order中的做法。但是这样做非常不优雅,并且会严重影响性能。同时,由于数据库中没有外键约束,一旦业务代码中出现bug,则会影响数据的一致性。
在这次的项目开发中,我使用Laravel的Eloquent ORM来实现外键的关系查询。
一些概念
我们首先来复习一些数据库系统导论中的一些概念
主键(Primary Key)
主关键字(Primary Key)是表中的一个或多个字段,它的值用于唯一地标识表中的某一条记录。主键通常名为id
,并且为自增。
外键(Foreign Key)
外键(Foreign Key)的作用是建立两个表之间的关联。下面这张图可以直观地展示外键的作用。

连接(Join)
连接(Join)将两张表中能关联起来的数据连接后返回。关于连接的更多内容请参考这篇文章:图解 SQL 里的各种 JOIN — 码志。
Laravel的Eloquent ORM中并没有实现Join,如果需要在Laravel中使用Join则需要在Query Builder中完成。请注意不要将本文所述内容和join混淆,Eloquent中的with方法的实现是通过模型中定义的关系另外进行一次查询,并没有使用join。
场景
我们假设一个网上商城中,每个用户有若干个订单,每个订单中有若干个产品。我们需要实现在一次查询中,通过用户id查询其所有订单和及订单中的所有产品,并返回一个如下格式的json。
{"result":[{ "id":50, "order_sn":"P6oz2yUWj2", "user_id":6, "shelf_id":34, "total_price":17, "pay_type":0, "transaction_no":"R4fl4NvG7m", "status":1, "created_at":"2019-01-17 20:39:36", "updated_at":"2019-01-17 20:39:36", "order_items":[ { "id":9, "order_id":50, "item_id":590, "item_amount":1, "item_name":"deserunt", "item_price":6, "created_at":"2019-01-17 20:39:37", "updated_at":"2019-01-17 20:39:37" } ], "users":{ "id":6, "name":"Blanca Schuster DDS", "email":"duane.watsica@example.com", "openid":"4WHOeUKHNt", "avatar":"iTRT2YAGni", "gender":0, "country":"YS16ISWMQU", "province":"mJWHFkbi2t", "city":"MiOqPu51aP", "email_verified_at":"2019-01-17 20:39:36", "created_at":"2019-01-17 20:39:36", "updated_at":"2019-01-17 20:39:36" } } ] }
编写数据库迁移
外键需要在编写迁移时就定义好,请注意代码中的注释。
Users表
class CreateUsersTable extends Migration { 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(); }); } }
Orders表
class CreateOrdersTable extends Migration { public function up() { Schema::create('orders', function (Blueprint $table) { $table->increments('id'); $table->string('order_sn')->comment('订单编号'); //定义一个unsignedInteger类型的user_id键,这个键必须和users表中的主键类型一致。 $table->unsignedInteger('user_id')->comment('用户id'); //将user_id定义为外键,该外键指向的是users表中的id键。 $table->foreign('user_id')->references('id')->on('users'); $table->integer('shelf_id')->comment('货架号'); $table->float('total_price')->comment('订单价格'); $table->integer('pay_type')->comment('支付方式,0:微信')->default(0); $table->string('transaction_no')->comment('支付交易单号'); $table->integer('status')->comment('订单状态')->default(0); $table->timestamps(); }); } }
Products表
class CreateProductsTable extends Migration { public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); //定义一个unsignedInteger类型的order_id键,这个键必须和orders表的主键类型一致。 $table->unsignedInteger('order_id')->comment('订单id'); //将order_id定义为外键,该外键指向的是orders表中的id键。 $table->foreign('order_id')->references('id')->on('orders'); $table->unsignedInteger('item_id')->comment('产品id'); $table->foreign('item_id')->references('id')->on('products'); $table->integer('item_amount')->comment('产品数量'); $table->string('item_name')->comment('产品名称'); $table->float('item_price')->comment('产品单价'); $table->timestamps(); }); } }
创建Model并定义关系
Orders模型
class Orders extends Model { public function users(){ return $this->belongsTo(User::class,'user_id'); } public function products(){ return $this->hasMany(Products::class,'order_id'); } }
Products模型
class Products extends Model { public function orders(){ return $this->belongsTo(Orders::class); } }
这部分代码表示order属于users,并且对应多个products;同时product属于orders。
查询
通过下面的语句即可查询某一用户的所有订单,及订单中的所有产品。
$products = Order::query()->where('user_id',1)->products; foreach($products as $i){ // }
预加载(Eager Loading)
如果我们dd
一下Order::query()->where('user_id',1)
的结果,我们会发现其中并不包含products。因为这部分结果只有在products属性被访问时才会被查询。如果我们需要将order及products的数据在一次查询中获取,则需要使用预加载(Eager Loading)。使用如下语句即可。
$result = $orders->with('orderItems','users');
此外,在https://laravel.com/docs/5.7/eloquent-relationships#eager-loading 所述的场景中,每次循环中都需要进行一次查询来获得book的author。而使用预加载则可以将所有对author的查询在一次查询中完成。
如果需要实现嵌套预加载,例如通过orderItems
中的product_id
从product表中查询产品信息,使用如下语句即可。
$result = 4orders->with('orderItems.product','users');w
2 Comments
cao
为毛今年一月份还在看 laravel?
Frank
当时做的ekeblock那个项目用的laravel。