在信息系统中有很多一对多的关系,常见的一个例子是一个订单(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":"[email protected]",
"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 = orders->with('orderItems.product','users');
发表回复/Leave a Reply