Optimize OneToOne mappings in Hibernate

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.

Example application

You may download the example Here. The zip contains a Gradle project with a gradle wrapper. To run the example execute gradlew run in the directory containing the gradlew file.

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void loadCustomerAndfetchCardAndContactPerson() throws HibernateException {
Session session = sessionFactory.openSession();
System.out.println("Load a single customer:");
Criteria crit = session.createCriteria(Customer.class);
@SuppressWarnings("unchecked")
Customer customer = (Customer) crit.setMaxResults(1).uniqueResult();
System.out.println("Fetch bonus card");
BonusCard bonusCard = customer.getBonusCard();
System.out.println("Bonus card code: " + bonusCard.getCardCode());
System.out.println("Fetch contact person");
ContactPerson contactPerson = customer.getContactPerson();
System.out.println("Contact person name: " + contactPerson.getName());
session.close();
}

Default OneToOne mapping

Let’s first have a look at the output generated when we use the default OneToOne mapping configuration:

1
2
3
4
5
@OneToOne()
private BonusCard bonusCard;
@OneToOne(mappedBy = "customer")
private ContactPerson contactPerson;

generates:
1
2
3
4
5
6
Load a single customer:
Hibernate: select this_.id as id1_2_2_, this_.bonusCard_id as bonusCar3_2_2_, this_.name as name2_2_2_, bonuscard2_.id as id1_0_0_, bonuscard2_.cardCode as cardCode2_0_0_, contactper3_.id as id1_1_1_, contactper3_.customer_id as customer3_1_1_, contactper3_.name as name2_1_1_ from Customer this_ left outer join BonusCard bonuscard2_ on this_.bonusCard_id=bonuscard2_.id left outer join ContactPerson contactper3_ on this_.id=contactper3_.customer_id limit ?
Fetch bonus card
Bonus card code: card_Code
Fetch contact person
Contact person name: contact_person_name

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.

Optimize OneToOne with fetch mode 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:

1
2
3
4
5
@OneToOne(fetch = FetchType.LAZY)
private BonusCard bonusCard;
@OneToOne(mappedBy = "customer",fetch = FetchType.LAZY)
private ContactPerson contactPerson;

Running the example gives us the following output:
1
2
3
4
5
6
7
8
Load a single customer:
Hibernate: select this_.id as id1_2_0_, this_.bonusCard_id as bonusCar3_2_0_, this_.name as name2_2_0_ from Customer this_ limit ?
Hibernate: select contactper0_.id as id1_1_1_, contactper0_.customer_id as customer3_1_1_, contactper0_.name as name2_1_1_, customer1_.id as id1_2_0_, customer1_.bonusCard_id as bonusCar3_2_0_, customer1_.name as name2_2_0_ from ContactPerson contactper0_ left outer join Customer customer1_ on contactper0_.customer_id=customer1_.id where contactper0_.customer_id=?
Fetch bonus card
Hibernate: select bonuscard0_.id as id1_0_0_, bonuscard0_.cardCode as cardCode2_0_0_ from BonusCard bonuscard0_ where bonuscard0_.id=?
Bonus card code: card_Code
Fetch contact person
Contact person name: contact_person_name

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.

How to get rid of the extra query for a OneToOne mapping

If at all possible then you should change the table structure so that the Contactperson id is stored in the Customer table.

1
2
3
4
5
@OneToOne(fetch = FetchType.LAZY)
private BonusCard bonusCard;
@OneToOne(fetch = FetchType.LAZY)
private ContactPerson contactPerson;

This gives Hibernate all the information it needs to generate lazy-proxies by only looking at the data in the Customer table.
1
2
3
4
5
6
7
8
Load a single customer:
Hibernate: select this_.id as id1_2_0_, this_.bonusCard_id as bonusCar3_2_0_, this_.contactPerson_id as contactP4_2_0_, this_.name as name2_2_0_ from Customer this_ limit ?
Fetch bonus card
Hibernate: select bonuscard0_.id as id1_0_0_, bonuscard0_.cardCode as cardCode2_0_0_ from BonusCard bonuscard0_ where bonuscard0_.id=?
Bonus card code: card_Code
Fetch contact person
Hibernate: select contactper0_.id as id1_1_0_, contactper0_.name as name2_1_0_ from ContactPerson contactper0_ where contactper0_.id=?
Contact person name: contact_person_name