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
Customer
toOrder
. - @JoinColumn: Specifies the foreign key (
customer_id
) in theorders
table that links each order to a customer. - The
cascade = CascadeType.ALL
ensures that any operations (such aspersist
,remove
) applied to theCustomer
entity will cascade to itsOrder
entities.
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
Customer
entity, we addedmappedBy = "customer"
to indicate thatCustomer
is the inverse side of the relationship, whileOrder
is the owning side. - In the
Order
entity, we used the@ManyToOne
annotation, which automatically manages the foreign key (customer_id
) in theorders
table that references theCustomer
. Since the default behavior without a@JoinColumn
is to create the foreign key with a standard naming convention, explicitly specifying the@JoinColumn
is 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
@OneToMany
on the parent entity and@ManyToOne
on 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.