Hibernate One-to-Many Relationship
Introduction
In Hibernate, a One-to-Many relationship represents an association where one entity is related to multiple instances of another entity. This is typically used when one object (the “parent”) is linked to a collection of objects (the “children”). In this article, we’ll cover the basics of the One-to-Many relationship, including unidirectional and bidirectional mappings, and provide examples for both.
Understanding One-to-Many Relationship in Hibernate
In database terms, a One-to-Many relationship means that for each row in the parent table,
there can be multiple corresponding rows in the child table. For example, one Customer may place
multiple Order records, where each customer can have several orders, but each order is linked to a single customer.
In Hibernate, this relationship is mapped using annotations or XML configuration. We will focus on the annotations-based approach, explaining both unidirectional and bidirectional mappings.
Unidirectional One-to-Many Mapping
In a unidirectional One-to-Many relationship, one entity holds a reference to a collection of another entity,
but not the other way around. For example, a Customer may have a collection of Orders,
but Order does not have a reference back to Customer.
Entity Classes
Let’s define two entities: Customer and Order. A customer can have many orders,
but the orders don’t need to reference the customer.
Customer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Getters, Setters, and Constructors omitted for brevity
}
Order.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_date")
private LocalDate orderDate;
@Column(name = "total")
private Double total;
// Constructors, Getters, and Setters
}
Explanation:
- @OneToMany: Indicates a one-to-many relationship from
CustomertoOrder. - @JoinColumn: Specifies the foreign key (
customer_id) in theorderstable that links each order to a customer. - The
cascade = CascadeType.ALLensures that any operations (such aspersist,remove) applied to theCustomerentity will cascade to itsOrderentities.
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
13
14
15
Customer customer = new Customer();
customer.setName("John Doe");
Order order1 = new Order();
order1.setOrderDate(LocalDate.of(2024, 10, 20));
order1.setTotal(150.75);
Order order2 = new Order();
order2.setOrderDate(LocalDate.of(2024, 10, 21));
order2.setTotal(200.50);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
entityManager.persist(customer);
Table Structure
Below is an example of how the customers and orders tables might look after data is inserted
using the entities described above.
Customers Table:
1
2
3
| id | name |
|-----|------------|
| 1 | John Doe |
Orders Table:
1
2
3
4
| id | order_date | total | customer_id |
|-----|------------|--------|-------------|
| 1 | 2024-10-20 | 150.75 | 1 |
| 2 | 2024-10-21 | 200.50 | 1 |
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
3
insert into customers (name) values (?);
insert into orders (order_date, total, customer_id) values (?, ?, ?);
insert into orders (order_date, total, customer_id) values (?, ?, ?);
When entityManager.persist(customer) is called, Hibernate first inserts the Customer entity
into the customers table, generating an ID for the customer (e.g., id = 1).
After the customer is persisted, Hibernate inserts the associated Order entities into the orders table,
assigning the customer_id that corresponds to the persisted Customer.
Bidirectional One-to-Many Mapping
In a bidirectional One-to-Many relationship, both entities hold references to each other.
This means that not only does Customer have a collection of Order objects,
but each Order also knows about its associated Customer.
Entity Classes
To make the relationship bidirectional, we need to modify the Order entity to reference the Customer
and make Customer the inverse side of the relationship.
Customer.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
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Getters and setters are omitted for brevity
public void addOrder(Order order) {
orders.add(order);
order.setCustomer(this);
}
public void removeOrder(Order order) {
orders.remove(order);
order.setCustomer(null);
}
}
Order.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_date")
private LocalDate orderDate;
@Column(name = "total")
private Double total;
@ManyToOne
private Customer customer;
// Getters, Setters, and Constructors omitted for brevity
}
Be cautious when using `toString` and `equals` with OneToMany relationships, as this can lead to infinite recursion due to bidirectional references. To avoid this issue, make sure to exclude the child collections from the `toString` method and any properties that reference the parent entity in the `equals` method.
Explanation:
- In the
Customerentity, we addedmappedBy = "customer"to indicate thatCustomeris the inverse side of the relationship, whileOrderis the owning side. - In the
Orderentity, we used the@ManyToOneannotation, which automatically manages the foreign key (customer_id) in theorderstable that references theCustomer. Since the default behavior without a@JoinColumnis to create the foreign key with a standard naming convention, explicitly specifying the@JoinColumnis optional unless a custom name is needed.
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
13
14
15
Customer customer = new Customer();
customer.setName("John Doe");
Order order1 = new Order();
order1.setOrderDate(LocalDate.of(2024, 10, 20));
order1.setTotal(150.75);
Order order2 = new Order();
order2.setOrderDate(LocalDate.of(2024, 10, 21));
order2.setTotal(200.50);
customer.addOrder(order1);
customer.addOrder(order2);
entityManager.persist(customer);
Table Structure
The table structure remains the same as in the unidirectional relationship,
with the customers and orders tables containing the same fields to maintain the relationship between them.
SQL Queries Generated by Hibernate
The insertion process is the same as in the unidirectional relationship.
Fetching Strategy
By default, Hibernate uses lazy loading for @OneToMany relationships,
meaning the associated collection (like the orders list) is not loaded until explicitly accessed.
You can change this behavior using the fetch attribute:
- Eager fetching:
1
@OneToMany(fetch = FetchType.EAGER)
This will load the associated entities immediately when the parent entity is fetched.
- Lazy fetching (default):
1
@OneToMany(fetch = FetchType.LAZY)
Lazy fetching is often preferred to avoid unnecessary database queries when the associated data is not needed immediately.
Summary
- In a unidirectional One-to-Many relationship, only the parent entity holds a reference to the collection of child entities, making it simpler but limiting reverse navigation.
- In a bidirectional One-to-Many relationship, both entities hold references to each other, allowing navigation from both sides.
- Use
@OneToManyon the parent entity and@ManyToOneon the child entity to map a bidirectional relationship. - Choose between lazy or eager fetching depending on your performance needs.
Conclusion
A One-to-Many relationship is a common pattern in database design, and Hibernate provides flexible ways to implement it. Whether you need a unidirectional or bidirectional relationship, Hibernate’s annotations and configuration options make it easy to manage. With the examples provided here, you should be able to implement and interact with One-to-Many relationships in your Hibernate applications effectively.