In this tutorial we’ll see how to map a one-to-many relationship with JPA and Hibernate, and the possible ways to do it. We’ll also learn what bidirectional relationships are, how they can create inconsistencies and and how to manage them.
Diagram
One-to-many mapping means that one row in one table maps to multiple rows in another table.
Let’s look at the following entity-relationship diagram to see a one-to-many association:
In this example we have a customer and an order and it is a typical relationship one to many, where a customer can have many orders. JPA provides us with two very useful annotations we will see shortly, @OneToMany and @ManyToOne.
Configuration
Dependencies
The dependencies for this project are clearly Hibernate and finally the H2 database.
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>6.2.4.Final</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> </dependency> </dependencies>
Hibernate
Let’s create the persistence.xml file, which is nothing more than the JPA persistence configuration. In this file we are going to define all the database connection parameters, we are also going to define where our entities are located.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> <persistence-unit name="default"> <class>com.lorenzomiscoli.hibernate_one_to_many.model.Customer</class> <class>com.lorenzomiscoli.hibernate_one_to_many.model.Order</class> <properties> <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver" /> <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:hibernate_one_to_many" /> <property name="jakarta.persistence.jdbc.user" value="sa" /> <property name="jakarta.persistence.jdbc.password" value="" /> <property name="hibernate.hbm2ddl.auto" value="update" /> </properties> </persistence-unit> </persistence>
Unidirectional @OneToMany with @JoinColumn
Consider the following mapping. We have a Customer class and an Order class.
With the annotation @OneToMany we define a relationship, while with @JoinColumn we define what is the foreign key in the Order class.
As you can see this relationship is unidirectional, because the Order entity doesn’t have the @ManyToOne annotation.
@Entity @Table(name = "customer") public class Customer { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "customer_id") private List<Order> orders = new ArrayList<>(); // getters and setters }
@Entity @Table(name="order") public class Order { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; // getters and setters }
If we persist a Customer and an Order:
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("default"); EntityManager em = emFactory.createEntityManager(); em.getTransaction().begin(); Customer customer = new Customer(); customer.getOrders().add(new Order()); em.persist(customer); em.getTransaction().commit(); em.close(); emFactory.close();
Hibernate will execute the following SQL statements:
insert into customer values ( ) insert into `order` values ( ) update `order` set customer_id=? where id=?
Hibernate first inserts the child entity without a foreign key, since it doesn’t hold this information, and after inserting it performs an update to update the relationship.
Not the most performant way isn’t it? Surely there is a better way to do this.
Bidirectional @OneToMany
Another way to map a one-to-many relationship is to use the @ManyToOne annotation to propagate all entity state changes.
@Entity @Table(name = "customer") public class Customer { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true) private List<Order> orders = new ArrayList<>(); public void addOrder(Order order) { orders.add(order); order.setCustomer(this); } public void removeOrder(Order order) { orders.remove(order); order.setCustomer(null); } // getters and setters }
@Entity @Table(name = "order") public class Order { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Integer id; @ManyToOne(fetch = FetchType.LAZY) private Customer customer; // getters and setters }
The Customer class has two methods (addOrder and removeOrder) which are used to synchronize both sides of the bidirectional association. When using bidirectional relationships, these methods should always be created to avoid inconsistencies in entity state.
The mappedBy attribute is defined in the @OneToMany annotation, to specify the reference side of the relationship.
The Order class has the @ManyToOne annotation, which is used to specify a many-to-one relationship, and also to create bidirectional relationships. In this case the Order class is the holder of the relationship, thanks to the use of the mappedBy attribute on the Customer class.
As noted in the JPA specification, it is good practice to mark many to one as the owner side of the relationship.
The @ManyToOne association uses FetchType.LAZY because otherwise we’ll fall back to EAGER fetching which is bad for performance.
Now, if we persist a Customer and an Order:
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("default"); EntityManager em = emFactory.createEntityManager(); em.getTransaction().begin(); Customer customer = new Customer(); customer.addOrder(new Order()); em.persist(customer); em.getTransaction().commit(); em.close(); emFactory.close();
Hibernate will execute the following SQL statements:
insert into customer values ( ) insert into `order` (customer_id) values (?)
We no longer have the update of the previous unidirectional association, so we can consider the @OneToMany bidirectional association a great way to map a one-to-many database relationship.
While bidirectional association is a great way to map a one-to-many relationship, it doesn’t mean you have to use it every time. It should only be used when we really need the collection on the parent side of the association.
Unidirectional @ManyToOne
It’s also possibile to map a one-to-many relationship with the @ManyToOne
annotation on the child side. If we try now to insert a Customer and Order:
EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("default"); EntityManager em = emFactory.createEntityManager(); em.getTransaction().begin(); Customer customer = new Customer(); Order order = new Order(); order.setCustomer(customer); em.persist(customer); em.persist(order); em.getTransaction().commit(); em.close(); emFactory.close();
Hibernate will execute the following SQL statements:
Hibernate: insert into customer (id) values (default) Hibernate: insert into "order" (customer_id,id) values (?,default)
And to get all the Orders associated with a Customer we can simply write the following JPQL query:
List<Order> orders = em.createQuery( "SELECT o FROM Order o WHERE o.customer.id = :customerId") .setParameter( "customerId", 1 ) .getResultList();
Hibernate will generate the following SQL query:
select order0_.id as id1_1_, order0_.customer_id as customer2_1_ from `order` order0_ where order0_.customer_id=?
Most of the time this is everything you need but sometimes this might not be what you’re looking for or simply a bidirectional association is what you need.
Conclusion
We have seen how to map a one-to-many relationship with JPA and Hibernate, and the possible ways to do it.
Additionally, we learned what bidirectional relationships are and how to manage them.
Source code is available on GitHub.