JPA @OneToOne relationship mapping and JPQL

Vitalii Shcherban
3 min readFeb 13, 2024

How @OneToOne is working in JPQL

Introduction

In database design, a one-to-one relationship refers to a type of relationship between two tables in a relational database where each record in one table is directly associated with exactly one record in the other table. This means that for every record in the first table, only one corresponding record exists in the second table, and vice versa.

When you are working with JPA relationships you can cause one specific case by using a custom JPQL query.

As an example, I will use Person and Address entities with shared key:

@Entity
@Table(name = "person")
public class Person {

@Id
@GeneratedValue
private Long id;

private String firstName;

private String lastName;

@OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
private Address address;

public Person() {
}

public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

// getters and setters are omitted
// ...
public void setAddress(Address address) {
if (address == null) {
if (this.address != null) {
this.address.setPerson(null);
}
} else {
address.setPerson(this);
}
this.address = address;
}
}
@Entity
@Table(name = "addresses")
public class Address {

@Id
@Column(name = "person_id")
private Long personId;

private String city;

private String country;

private String street;

@OneToOne
@MapsId
@JoinColumn(name = "person_id")
private Person person;

public Address() {
}

// getters and setters are omitted
// ...
public void setPerson(Person person) {
if (person == null) {
this.personId = null;
this.person = null;
} else {
this.personId = person.getId();
this.person = person;
}
}
}

Default OneToOne fetching

By default *ToOne(OneToOne, ManyToOne) relations are EAGER. That means that it should be fetched automatically by ORM. In Hibernate single query with left outer join is generated.

entityManager.find(Person.class, 1L);
select person0_.id         as id1_3_0_,
person0_.first_name as first_na2_3_0_,
person0_.last_name as last_nam3_3_0_,
address1_.person_id as person_i1_0_1_,
address1_.city as city2_0_1_,
address1_.country as country3_0_1_,
address1_.street as street4_0_1_
from person person0_
left outer join addresses address1_ on person0_.id = address1_.person_id
where person0_.id=?

JPQL Query

If you want to use a custom JPQL query, the OneToOne relation won't be fetched by default strategy(left outer join), and 2 SQL queries will be executed even with @OneToOne(fetch = EAGER).

ORM transfers responsibility to you, to fetch the relation in the query.

entityManager.createQuery("select p from Person p where p.id = ?1", Person.class)
.setParameter(1, 1L)
.getSingleResult();
select person0_.id as id1_3_,
person0_.first_name as first_na2_3_,
person0_.last_name as last_nam3_3_
from person person0_
where person0_.id =?

select address0_.person_id as person_i1_0_0_,
address0_.city as city2_0_0_,
address0_.country as country3_0_0_,
address0_.street as street4_0_0_
from addresses address0_
where address0_.person_id=?

To handle this the join fetch should be added to JPQL:

entityManager.createQuery("select p from Person p join fetch p.address where p.id = ?1", Person.class)
.setParameter(1, 1L)
.getSingleResult();
select person0_.id         as id1_3_0_,
address1_.person_id as person_i1_0_1_,
person0_.first_name as first_na2_3_0_,
person0_.last_name as last_nam3_3_0_,
address1_.city as city2_0_1_,
address1_.country as country3_0_1_,
address1_.street as street4_0_1_
from person person0_
inner join addresses address1_ on person0_.id = address1_.person_id
where person0_.id=?

Summary

  • One-to-one relationship in database design links each record in one table to exactly one record in another.
  • JPA relationships allow manipulation of these relationships using custom JPQL queries.
  • The default strategy for *ToOne relationships is eager-loading, generating a single query with left outer join in Hibernate.
  • Custom JPQL queries may result in two SQL queries, shifting the responsibility of fetching to the developer.
  • The join fetch clause in the JPQL query optimizes performance by fetching related entities in a single query.
  • The nuance about how @OneToOne is working with JPQL is key to do not pollute your DB with unnecessary SQL queries and improving your application performance

References

--

--