Post

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 to Order.
  • @JoinColumn: Specifies the foreign key (customer_id) in the orders table that links each order to a customer.
  • The cascade = CascadeType.ALL ensures that any operations (such as persist, remove) applied to the Customer entity will cascade to its Order 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 added mappedBy = "customer" to indicate that Customer is the inverse side of the relationship, while Order is the owning side.
  • In the Order entity, we used the @ManyToOne annotation, which automatically manages the foreign key (customer_id) in the orders table that references the Customer. 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.

© 2024 Java Tutorial Online. All rights reserved.