Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Primary key of owning side as a join column

Tags:

NOTE: Topic is lengthy but detailed and may come in handy if you use Doctrine2 and oneToOne relationships.

Recently I came across a problem in Doctrine:

I created User and UserData objects with oneToOne bidirectional relationship:

User: ...   oneToOne:     userdata:       targetEntity: UserData       mappedBy: user  UserData: ...   oneToOne:     user:       targetEntity: User       inversedBy: userdata 

So UserData was the owning side with user_id column in it:

user: id, ... userdata: id, user_id, ... 

This created a problem, where every time you fetch a User object (Single user, collection of user or collection of other object with user joined on it) Doctrine would lazy load a UserObject for each User.

Issue described here:

  • How to prevent Doctrine from lazy loading one to one relationsip?
  • http://groups.google.com/group/doctrine-user/browse_thread/thread/7e421a2b189f0ea7
  • https://github.com/doctrine/doctrine2/issues/4389

Proposed solution described here:

  • https://github.com/doctrine/doctrine2/issues/2364

So there are 3 ways around this:

  1. Wait and see if proposed solution is addressed in Doctrine and fixed in future releases (may not happen)
  2. Manually left join UserData to User in every query (still waste of resources, dont need UserData)
  3. Switch inverse side and make User the owning side.

I decided to go with #3. So my schema relationship now looks like this:

User: ...   oneToOne:     userdata:       targetEntity: UserData       inversedBy: user  UserData: ...   oneToOne:     user:       targetEntity: User       mappedBy: userdata 

This means that my tables now look like this:

user: id, userdata_id, ... userdata: id, ... 

I decided that instead of having Userdata.id autoincremented, I'll set it manually and match it with user.id. This means that UserData.id will always match user.id.

Question Can I use user.id (a primary autoincremented key) as joinColum instead of userdata_id since they will always have the same value? Do you see any potential issues with this way of doing things?

Any other tips or opinions about this issue is greatly welcomed and appreciated.

like image 901
DavidW Avatar asked Mar 24 '12 02:03

DavidW


People also ask

What is owning side in Hibernate?

The owning side of the relation tracked by Hibernate is the side of the relation that owns the foreign key in the database.

What does mappedBy mean?

mappedBy tells Hibernate how to create instances of your entities and load the data into them. It should refer to the field name in the class that you are annotating, PersonDetail in this instance, where the relationship is defined.

What is mappedBy one to many?

The mappedBy attribute characterizes a bidirectional association and must be set on the parent-side. In other words, for a bidirectional @OneToMany association, set mappedBy to @OneToMany on the parent-side and add @ManyToOne on the child-side referenced by mappedBy .

What is bidirectional relationship JPA?

Overview. JPA Relationships can be either unidirectional or bidirectional. This simply means we can model them as an attribute on exactly one of the associated entities or both. Defining the direction of the relationship between entities has no impact on the database mapping.


2 Answers

You could also force partial objects, to get rid off lazy-loading:

use Doctrine\ORM\Query;  //... $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); 
like image 72
Mick Avatar answered Oct 22 '22 10:10

Mick


This is a known issue for OneToOne associations. There is a github discussion about this that is worth reading. A solution (pull request) was proposed and rejected.

Recommendation

Your question suggests the same solution proposed by the contributors to Doctrine: change the owning side of the relationship.

Other Options Explored

I had a similar problem with an entity called Version that had a OneToOne bidirectional relationship with Settings. Every time I queried Version (say for 10 specific version records), Doctrine would do additional queries for the joined Settings (as if it was Lazy Loading these entities). This happened, even though I did not reference Settings anywhere, e.g. $Version->getSettings()->getSomeProperty().

Manual JOIN

The only "solution" (hack) that works for me is to manually included a JOIN for this Settings entity every time I did a query on Version. But since I don't need the extra entity (in this case), that would still be a single extra unnecessary query, every time I query this table in different ways.

Extra Lazy

Based on other suggestions, I thought that if I explicitly specified this relationship as "extra lazy" it would work, e.g.

/**  * @ManyToMany(targetEntity="Settings", mappedBy="version", fetch="EXTRA_LAZY")  */ 

But this doesn't affect hydration. See the Doctrine Docs for more details about what EXTRA_LAZY does.

Hydration Type: HYDRATE_ARRAY

What helped in my case (when I didn't need an entity), was to specify that I wanted to fetch as an array (rather than object).

$query = $queryBuilder->getQuery(); $query->getResult(Query:HYDRATE_ARRAY); 

This returns an array, and as a result it doesn't lazy load the OneToOne associations. However, in other contexts where I need the entity object, I had to explicitly JOIN the entity (despite not wanting it).

like image 22
Chadwick Meyer Avatar answered Oct 22 '22 10:10

Chadwick Meyer