Hibernate One-to-One Relationship
Introduction
In Hibernate, a One-to-One relationship refers to an association where one entity is related to exactly one other entity. This is typically used when two entities are closely related, and each instance of one entity corresponds to exactly one instance of the other. In this article, we’ll explore how to model a one-to-one relationship in Hibernate, including both unidirectional and bidirectional mappings, along with examples to clarify each concept.
Understanding One-to-One Relationship in Hibernate
In a database, a one-to-one relationship between tables means that for each row in one table, there is at most one corresponding row in another table. For example, a User may have a corresponding Profile where each user has a unique profile, and each profile is associated with only one user.
In Hibernate, you can map these relationships using annotations or XML configuration. We’ll focus on annotations here and cover both unidirectional and bidirectional relationships.
Unidirectional One-to-One Mapping
In a unidirectional one-to-one relationship, one entity holds a reference to the other entity,
but not the other way around. For example, if the User
entity holds a reference to the Profile
entity,
but Profile
doesn’t reference User
.
Entity Classes
Let’s create two entities: User
and Profile
. A user has exactly one profile,
and a profile is linked to only one user.
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String username;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id", referencedColumnName = "id")
private Profile profile;
// Getters, Setters, and Constructors omitted for brevity
}
Profile.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "profiles")
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "address")
private String address;
@Column(name = "phone_number")
private String phoneNumber;
// Getters, Setters, and Constructors omitted for brevity
}
Explanation
- @OneToOne: Declares a one-to-one relationship between
User
andProfile
. - @JoinColumn: Specifies the foreign key (
profile_id
) in theusers
table that references theid
column of theprofiles
table. - CascadeType.ALL: Ensures that operations performed on the
User
entity (like persist) are cascaded to the associatedProfile
entity.
Objects creation
In this example, we will use the EntityManager
to persist the objects,
though various persistence approaches are also possible.
1
2
3
4
5
6
7
8
9
10
11
12
// Create Profile object
Profile profile = new Profile();
profile.setAddress("123 Main St");
profile.setPhoneNumber("123-456-7890");
// Create User object
User user = new User();
user.setUsername("john_doe");
user.setProfile(profile);
// Save User to the database
entityManager.persist(user);
Table Structure
Below is an example of how the users
and profiles
tables might look after data is inserted using the entities
described above.
Users Table:
1
2
3
4
5
| id | username | profile_id |
|-----|-----------|------------|
| 1 | john_doe | 1 |
| 2 | jane_doe | 2 |
| 3 | alex_smith| NULL |
Profiles Table:
1
2
3
4
| id | address | phone_number |
|-----|--------------|---------------|
| 1 | 123 Main St | 123-456-7890 |
| 2 | 456 Maple Ave| 987-654-3210 |
SQL Queries Generated by Hibernate
If you have hibernate.hbm2ddl.auto=update
or spring.jpa.hibernate.ddl-auto=update
enabled,
you will see the corresponding inserts reflecting the data added to the tables.
1
2
insert into profiles (address, phone_number) values (?, ?);
insert into users (profile_id, username) values (?, ?);
When entityManager.persist(user)
is called, Hibernate first inserts the Profile
entity into the profiles
table,
generating an id for the profile (e.g., id = 1). After the profile is persisted, Hibernate inserts the User
entity
into the users
table, using the generated profile_id
from the Profile
table.
Bidirectional One-to-One Mapping
In a bidirectional one-to-one relationship, both entities hold references to each other. For example,
if the User
entity holds a reference to the Profile
entity, and vice versa, forming a two-way relationship.
Entity Classes
To make the relationship bidirectional, you need to add a reference to User
in the Profile
class,
and annotate it with @OneToOne
as well.
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String username;
@OneToOne(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Profile profile;
// Getters, Setters, and Constructors omitted for brevity
public void addProfile(Profile profile) {
profile.setUser(this);
this.profile = profile;
}
public void removeProfile() {
if (profile != null) {
profile.setUser(null);
this.profile = null;
}
}
}
Profile.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
@Table(name = "profiles")
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "address")
private String address;
@Column(name = "phone_number")
private String phoneNumber;
@OneToOne
@JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
//Getters and setters are omitted for brevity
}
Explanation
- @OneToOne(mappedBy = “user”): Indicates that the
Profile
entity is the owner of the relationship, whileUser
contains the back reference. - CascadeType.ALL: Ensures that operations performed on the
User
entity cascade to the associatedProfile
entity. - orphanRemoval = true: If the
Profile
is removed from theUser
, the profile entity will also be deleted. - addProfile() and removeProfile(): These helper methods maintain consistency in the relationship
by setting the references correctly when adding or removing a
Profile
.
Object Creation
In this example, we will use the EntityManager
to persist the objects,
though various persistence approaches are also possible.
1
2
3
4
5
6
7
8
9
10
11
12
// Create Profile object
Profile profile = new Profile();
profile.setAddress("123 Main St");
profile.setPhoneNumber("123-456-7890");
// Create User object and link it with the profile
User user = new User();
user.setUsername("john_doe");
user.addProfile(profile);
// Save User to the database
entityManager.persist(user);
Table Structure
The following tables might represent the users
and profiles
data after being persisted.
Users Table:
1
2
3
| id | username |
|-----|-----------|
| 1 | john_doe |
Profiles Table:
1
2
3
| id | address | phone_number | user_id |
|-----|--------------|---------------|---------|
| 1 | 123 Main St | 123-456-7890 | 1 |
SQL Queries Generated by Hibernate
With hibernate.hbm2ddl.auto=update
enabled, you will see these SQL queries when inserting the data:
1
2
insert into users (username) values (?);
insert into profiles (address, phone_number, user_id) values (?, ?, ?);
When entityManager.persist(user)
is called, Hibernate first inserts the User
entity into the users
table,
generating an id
for the user. Then, Hibernate inserts the Profile
entity into
the profiles
table, referencing the generated user_id
.
Fetching Strategy
By default, Hibernate uses eager loading for @OneToOne
relationships, meaning that the associated entity
is loaded immediately with the main entity. You can change this behavior using the fetch attribute:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Table(name = "users")
public class User {
@OneToOne(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private Profile profile;
// Fields, Constructors, Getters, Setters and Additional Methods omitted for brevity
}
1
2
3
4
5
6
7
8
9
10
@Entity
@Table(name = "profiles")
public class Profile {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
// Fields, Constructors, Getters and Setters Methods omitted for brevity
}
Summary
- In a unidirectional one-to-one relationship, only one entity holds a reference to the other. This is simpler but lacks a reverse association.
- In a bidirectional one-to-one relationship, both entities have references to each other, allowing for navigation in both directions.
- Use @OneToOne with
@JoinColumn
to define the foreign key, and setcascade
to control cascading operations. - You can choose between lazy or eager fetching depending on performance needs.
Conclusion
Hibernate’s one-to-one relationships are an essential tool when modeling close associations between entities. Whether you choose a unidirectional or bidirectional relationship depends on the specific needs of your application, such as whether you need navigation in both directions or not. With this knowledge and the examples provided, you should be able to effectively map one-to-one relationships in your Hibernate applications.