laravel-mongodb模型关系详解:嵌入式文档与引用关系对比

laravel-mongodb模型关系详解:嵌入式文档与引用关系对比

【免费下载链接】laravel-mongodb 【免费下载链接】laravel-mongodb 项目地址: https://gitcode.com/gh_mirrors/lar/laravel-mongodb

在使用MongoDB数据库时,开发者常常需要在嵌入式文档(Embedded Documents)和引用关系(Referenced Relationships)之间做出选择。这两种关系模型各有适用场景,选择不当可能导致性能瓶颈或数据一致性问题。本文将深入对比laravel-mongodb中的嵌入式文档(embedsOne/embedsMany)与引用关系(hasOne/hasMany),帮助开发者根据业务场景做出最优决策。

关系模型核心差异

嵌入式文档和引用关系的本质区别在于数据存储方式:嵌入式文档将关联数据直接存储在父文档内部,而引用关系通过ID指针关联不同集合的文档。

嵌入式文档

嵌入式文档通过embedsOne(一对一)和embedsMany(一对多)方法定义,数据存储在父文档的字段中。例如,宇宙飞船(SpaceShip)可以嵌入多个货物(Cargo)文档:

// [docs/includes/eloquent-models/relationships/embeds/SpaceShip.php](https://link.gitcode.com/i/258fa40904de843a9e6be72381b72700)
namespace App\Models;

use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\EmbedsMany;

class SpaceShip extends Model
{
    protected $connection = 'mongodb';

    public function cargo(): EmbedsMany
    {
        return $this->embedsMany(Cargo::class);
    }
}

存储结构示例:

{
  "_id": "ObjectId(...)",
  "name": "Millennium Falcon",
  "cargo": [
    {"name": "spice", "weight": 50, "_id": "ObjectId(...)"},
    {"name": "hyperdrive", "weight": 25, "_id": "ObjectId(...)"},
  ]
}

引用关系

引用关系通过hasOne/hasMany(一对多)和belongsTo(反向关联)定义,关联数据存储在独立集合中,通过外键(如planet_id)关联。例如,行星(Planet)拥有多个卫星(Moon):

// [docs/includes/eloquent-models/relationships/one-to-many/Planet.php](https://link.gitcode.com/i/e3b1fb133eecfd811e12f219c4fa70c3)
namespace App\Models;

use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\HasMany;

class Planet extends Model
{
    protected $connection = 'mongodb';

    public function moons(): HasMany
    {
        return $this->hasMany(Moon::class);
    }
}

卫星集合中的文档通过planet_id关联行星:

// moons集合
{
  "_id": "ObjectId(...)",
  "name": "Europa",
  "planet_id": "ObjectId(...)" // 引用行星ID
}

性能对比与适用场景

读取性能

嵌入式文档读取性能显著优于引用关系,因为一次数据库查询即可获取所有关联数据。例如,获取宇宙飞船及其货物时,嵌入式模型只需查询space_ships集合,而引用关系需要分别查询planetsmoons集合(产生N+1查询问题)。

适用场景:关联数据需频繁一起读取的场景(如订单-订单项)。

写入性能

  • 嵌入式文档:写入时会锁定整个父文档,适用于小数据量更新。大数据量嵌入可能导致文档过大(MongoDB建议单文档不超过16MB)。
  • 引用关系:子文档可独立写入,适合高并发更新场景(如社交媒体评论)。

数据一致性

嵌入式文档天然支持原子更新,可通过单次操作更新父文档及所有嵌入数据:

// 原子更新嵌入式文档
$spaceship->cargo()->update(['weight' => 60]);

引用关系需手动维护一致性,可通过事务(Transactions)确保多文档更新的原子性:

// [docs/includes/fundamentals/transactions/TransactionsTest.php](https://link.gitcode.com/i/fd2cc80b8375f6bb58e27db4341b0b6e)
DB::transaction(function () {
    $planet->update(['name' => 'Jupiter']);
    $planet->moons()->update(['orbital_period' => 173]);
});

功能支持对比

功能特性嵌入式文档引用关系
跨集合关联❌ 不支持✅ 支持(可跨数据库)
延迟加载(Lazy Loading)❌ 已随父文档加载✅ 支持,需额外查询
分页查询✅ 支持(内存分页)✅ 支持(数据库级分页)
索引支持✅ 支持嵌入字段索引✅ 支持外键索引
数据独立访问❌ 需通过父文档访问✅ 可直接查询子文档集合

决策指南与最佳实践

优先选择嵌入式文档当:

  1. 关联数据与父文档生命周期一致(如文章-评论)。
  2. 需频繁一起读取,且数据量较小(如用户-个人资料)。
  3. 需原子更新关联数据(如订单状态变更)。

优先选择引用关系当:

  1. 关联数据需独立访问或跨集合查询(如用户-订单)。
  2. 数据量较大或更新频繁(如社交媒体动态)。
  3. 存在多对多关系(需通过belongsToMany实现)。

关系定义代码示例

嵌入式文档完整示例

// 父模型:SpaceShip
namespace App\Models;

use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\EmbedsMany;

class SpaceShip extends Model
{
    protected $connection = 'mongodb';

    public function cargo(): EmbedsMany
    {
        return $this->embedsMany(Cargo::class);
    }
}

// 嵌入模型:Cargo
class Cargo extends Model
{
    protected $fillable = ['name', 'weight'];
}

// 使用示例
$ship = SpaceShip::create(['name' => 'Millennium Falcon']);
$ship->cargo()->create(['name' => 'spice', 'weight' => 50]);

引用关系完整示例

// 父模型:Planet
namespace App\Models;

use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\HasMany;

class Planet extends Model
{
    protected $connection = 'mongodb';

    public function moons(): HasMany
    {
        return $this->hasMany(Moon::class);
    }
}

// 子模型:Moon
class Moon extends Model
{
    protected $fillable = ['name', 'planet_id'];
}

// 使用示例
$planet = Planet::create(['name' => 'Jupiter']);
$planet->moons()->create(['name' => 'Ganymede']);

官方文档与进阶资源

通过合理选择关系模型,可充分发挥MongoDB的文档数据库优势。嵌入式文档适合数据聚合场景,引用关系适合灵活扩展场景,实际开发中也可结合两者(如混合使用嵌入和引用)以满足复杂业务需求。

【免费下载链接】laravel-mongodb 【免费下载链接】laravel-mongodb 项目地址: https://gitcode.com/gh_mirrors/lar/laravel-mongodb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值