Reputation: 2618
I've been reading a lot about Doctrine lately and I have seen a general suggestion to avoid bi-directional One-To-One associations. This page (from June 2015) states:
... you should avoid bidirectional one-to-one associations in Doctrine.
The inverse side of a one-to-one association cannot be lazy loaded.
I am assuming that cannot be lazy loaded means something like must be eager-loaded .
The page is also called "Doctrine Performance Traps", so it looks scary :)
Since the manual on one-to-one doesn't mention this, I have decided to test and see for myself. I found a couple of classes in our test app that were already using one-to-one associations, so this was easy:
class Recipe
{
/**
* @ORM\OneToOne(targetEntity="Image")
* @ORM\JoinColumn(name="image_id", unique=true, referencedColumnName="id")
*/
private $image;
}
class Image
{
/**
* @ORM\OneToOne(targetEntity="Recipe")
* @ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
*/
private $recipe;
}
Next, I try to select an image in my controller:
$image = $em->getRepository('AppBundle:Image')->find(658);
In Symfony profiler I see one sql query with no joins. In the dumped image data, under Recipe, I see:
+__isInitialized__: false
Which would mean that the Recipe entity was lazy-loaded?
The same happens when I load the Recipe: Image gets lazy-loaded.
Adding inversedBy
to any of the entities doesn't seem to make a difference.
It seems that either the article I was reading was incorrect, or something has changed in Doctrine since then, or I don't understand lazy-loading.
Questions:
I understand that a lot of times I can get away without using bidirectional mapping, but sometimes I really really need to have them and I am just trying to understand the price of this decision.
Thanks!
Upvotes: 1
Views: 3046
Reputation: 404
Your mapping for a bidirectional one-to-one association is not correct. Usually you have the @JoinColumn
only on one end of the association, not on both ends:
<?php
class Recipe
{
/**
* @ORM\OneToOne(targetEntity="Image", mappedBy="recipe")
*/
private $image;
}
class Image
{
/**
* @ORM\OneToOne(targetEntity="Recipe", inversedBy="image")
* @ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
*/
private $recipe;
}
If you set up your mapping like this and load the Recipe
you will see the expected JOIN
with the Image
table.
Upvotes: 1
Reputation: 1046
I think you are making some confusion between lazy and eager loading.
The Lazy Loading does not load the related data with the join statement: when you try to access to the child object (from the parent) you actually make a call to a proxy object that will trigger a select statement with the proper conditions. Lazy loading should be avoided when you want to fetch data that will be accessed for sure.
The Eager Loading loads the related data with a join statement in a single query. This will help you solving the "n+1" overhead.
Let's assume you have some categories and subcategories splitted in one table (I know, this is weird for a database normalization approach but will help us get the score): you are going to display a menu with the categories AND the subcategories. With the Eager Loading, for each category you will make a select statement for getting the subcategory data (i.e. the name you will display on your menu).
For what regards the bidirectional one-to-one relationship, the reason why loading it bidirectionally cause an overhead is pretty simple: let's assume that A has a one-to-one with B. When you load A you are gonna make a proxy object that points straight to B. If you set a bidirectional relationship, your proxy object B will contain an (unuseful) A proxy object too.
Hope this helps (and that I didn't make any mistake actually) (:
Upvotes: 1