Doctrine MongoDB ODM中引用映射与嵌入映射的区别及一对多实现咨询
Hey there! Let's break down the key differences between Reference Mapping and Embedded Mapping in Doctrine MongoDB ODM, then walk through how to implement a one-to-many relationship using both approaches—since which one you pick depends entirely on your use case.
1. Data Storage Model
- Embedded Mapping: Related documents are stored inside the parent document as an array of sub-documents. They don't exist as separate entries in the collection.
- Reference Mapping: Related documents are stored as separate entries in their own collection, and the parent document holds a reference (like an ID) pointing to them.
2. Query Performance
- Embedded: Faster for fetching the parent + all related data in a single query—no joins or additional lookups needed. But if the embedded array grows too large, it can bloat the parent document and slow things down.
- Reference: Requires additional queries (or using
fetch: EAGERto load references upfront) to retrieve related data. This is slower for full parent+child loads, but better if you often need to query the child documents independently.
3. Data Consistency & Atomicity
- Embedded: All changes to parent and embedded docs can be done in a single atomic operation. You don't have to worry about orphaned child docs here.
- Reference: Updating parent and child docs requires separate operations. You'll need to handle orphaned references manually (or use Doctrine's orphan removal features if applicable).
4. Use Case Fit
- Embedded: Ideal for tightly coupled data that's never accessed without its parent (e.g., a user's address list, order line items).
- Reference: Better for independent data that needs to be queried or modified on its own (e.g., a user's blog posts, comments on a post).
Let's use a common example: a User who has multiple Address entries (embedded) and multiple Post entries (referenced).
Option 1: Embedded Mapping (One-to-Many)
Here, Address is embedded directly inside the User document.
First, define the embedded document:
<?php namespace App\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\EmbeddedDocument */ class Address { /** @ODM\Id */ private $id; /** @ODM\Field(type="string") */ private $street; /** @ODM\Field(type="string") */ private $city; // Getters and setters }
Then the parent User document with an embedded collection:
<?php namespace App\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\Common\Collections\ArrayCollection; /** @ODM\Document */ class User { /** @ODM\Id */ private $id; /** @ODM\Field(type="string") */ private $name; /** * @ODM\EmbedMany(targetDocument=Address::class) */ private $addresses; public function __construct() { $this->addresses = new ArrayCollection(); } // Add/remove methods for addresses public function addAddress(Address $address): void { $this->addresses->add($address); } public function removeAddress(Address $address): void { $this->addresses->removeElement($address); } // Getters and setters }
When you persist the User, all Address entries are saved inside the same MongoDB document.
Option 2: Reference Mapping (One-to-Many)
For independent entities like Post, we'll use references. Here, we can map it in two ways:
A. Parent (User) holds references to children (Post)
<?php namespace App\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\Common\Collections\ArrayCollection; /** @ODM\Document */ class User { /** @ODM\Id */ private $id; /** @ODM\Field(type="string") */ private $name; /** * @ODM\ReferenceMany( * targetDocument=Post::class, * mappedBy="author", * fetch="LAZY" // Default, loads posts only when accessed * ) */ private $posts; public function __construct() { $this->posts = new ArrayCollection(); } // Add/remove methods public function addPost(Post $post): void { if (!$this->posts->contains($post)) { $this->posts->add($post); $post->setAuthor($this); } } // Getters and setters }
Then the Post document references the User:
<?php namespace App\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\Document */ class Post { /** @ODM\Id */ private $id; /** @ODM\Field(type="string") */ private $title; /** * @ODM\ReferenceOne( * targetDocument=User::class, * inversedBy="posts", * cascade={"persist"} * ) */ private $author; // Getters and setters public function setAuthor(User $author): void { $this->author = $author; } }
Key Notes for Reference Mapping:
- Use
fetch="EAGER"if you always need the related data loaded with the parent (but be cautious of performance hits). - The
cascade={"persist"}option lets you persist theUserand automatically persist its associatedPosts. - To avoid orphaned posts, you can add
orphanRemoval=trueto the@ReferenceManyannotation—this deletes posts when they're removed from the user's collection.
- Go with Embedded Mapping if your one-to-many data is tightly coupled, doesn't need independent queries, and you want atomic updates.
- Choose Reference Mapping if the child entities need to exist on their own, be queried separately, or might grow too large to embed without bloating the parent document.
内容的提问来源于stack exchange,提问作者Roman Newaza

