Trying to optimize OneToOne mappings by setting the fetch mode to lazy does not always work as expected. I’ve created a guide about how to optimize OneToOne mappings. This guide takes a closer look at how lazy fetching is affected by the owner of a relation.
While working with OneToOne mappings I’ve encountered some surprises when looking at the queries generated by Hibernate. Most of the time this happens when I try to optimize OneToOne mappings by setting the fetchType to lazy. It appears that even when a OneToOne mapping is set to lazy Hibernate will still perform an eager load. I’ve created a small example application to simulate this behavior.
The example contains 3 entities: Customer, BonusCard and ContactPerson. A Customer contains a OneToOne mapping to BonusCard and to ContactPerson. The important difference here is that Customer owns the relation to BonusCard while the relation to ContactPerson is owned by ContactPerson. Translated to a relational model this means that the Customer table contains a BonusCard id but does not contain the ContactPerson id. The ContactPerson table contains a Customer id which is used to map the relation.
After adding some test data the application will retrieve a customer and display some information about the bonus card and the contact person. We are mainly interested in the queries generated when loading the customer and fetching the BonusCard and ContactPerson entities.
Let’s first have a look at the output generated when we use the default OneToOne mapping configuration:
Hibernate loads all the data in one query when we load the Customer entity. It does so by performing a join on the ContactPerson and BonusCard tables. The default fetch strategy for a OneToOne mapping is eager so this output could be expected. Things start to change however when we optimize OneToOne mappings by changing the fetch strategy to lazy.
Suppose that in our application BonusCard and ContactPerson are large entities which are not often used. We would like to load them only when needed so we change the fetch mode to lazy:
Running the example gives us the following output:
BonusCard works as expected. The entity is not loaded when we load the Customer. Instead it is loaded when we actually request it. ContactPerson however is still loaded when we load the Customer!
It turns out that in order for Hibernate to create a lazy-fetch proxy it needs to know if the target entity exists. If a relation targets an existing entity a proxy is generated which will perform a lazy load when needed. If no entity exists for the relation (maybe not every customer has a contact person?) then no proxy is generated and the field is set to null.
Keeping this in mind it becomes obvious why Hibernate needs to fetch the ContactPerson when it loads the Customer. When loading the Customer Hibernate knows that a valid BonusCard exists because the BonusCard id is in the Customer table. It does not have this information about Contactperson so Hibernate has no choice but to perform an additional query to the ContactPersonTable.
If at all possible then you should change the table structure so that the Contactperson id is stored in the Customer table.
This gives Hibernate all the information it needs to generate lazy-proxies by only looking at the data in the Customer table.