Post

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 and Profile.
  • @JoinColumn: Specifies the foreign key (profile_id) in the users table that references the id column of the profiles table.
  • CascadeType.ALL: Ensures that operations performed on the User entity (like persist) are cascaded to the associated Profile 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, while User contains the back reference.
  • CascadeType.ALL: Ensures that operations performed on the User entity cascade to the associated Profile entity.
  • orphanRemoval = true: If the Profile is removed from the User, 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 set cascade 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.

© 2024 Java Tutorial Online. All rights reserved.