Most efficient way to map a @OneToMany relationship with JPA and Hibernate

Rajib Rath
11 min readNov 20, 2019

--

I chose to open this post with this quote because I’m a fan of Linus Torvalds.😉

This is my first ever article. In this I will be covering all possible case in One-to-Many/Many-to-One entity association. Remaining Many-to-Many and One-to-One will be covered in next articles.

I hope this will definitely help every newbies who want to learn jpa/hibernate, Please read the whole piece :P

NOTE:

Here I have covered all possible cases of One-to-Many/Many-to-One mapping. Among all , Bidirectional `@OneToMany` association is the best way to map a one-to-many database relationship.

The hibernate association classified into One-to-One, One-to-Many/Many-to-One and Many-to-Many.

  • The direction of a relationship can be either bidirectional or unidirectional.
  • A bidirectional relationship has both an owning side and an inverse side.
  • A unidirectional relationship has only an owning side. The owning side of a relationship determines how the Persistence run time makes updates to the relationship in the database.

Unidirectional Relationships:

  • Unidirectional is a relation where one side does not know about the relation.
  • In a unidirectional relationship, only one entity has a relationship field or property that refers to the other. For example, Line Item would have a relationship field that identifies Product, but Product would not have a relationship field or property for Line Item. In other words, Line Item knows about Product, but Product doesn’t know which Line Item instances refer to it.

Bidirectional Relationships:

  • Bidirectional relationship provides navigational access in both directions, so that you can access the other side without explicit queries.
  • In a bidirectional relationship, each entity has a relationship field or property that refers to the other entity. Through the relationship field or property, an entity class’s code can access its related object. If an entity has a related field, the entity is said to “know” about its related object. For example, if Order knows what Line Item instances it has and if Line Item knows what Order it belongs to, they have a bidirectional relationship.

Bidirectional relationships must follow these rules.

  • The inverse side of a bidirectional relationship must refer to its owning side(Entity which contains the foreign key) by using the mappedBy element of the @OneToOne, @OneToMany, or @ManyToMany annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.
  • The many side of @ManyToOne bidirectional relationships must not define the mappedBy element. The many side is always the owning side of the relationship.
  • For @OneToOne bidirectional relationships, the owning side corresponds to the side that contains @JoinColumn i.e the corresponding foreign key.
  • For @ManyToMany bidirectional relationships, either side may be the owning side.

@OneToMany relationship with JPA and Hibernate

Simply put, one-to-many mapping means that one row in a table is mapped to multiple rows in another table.

When to use one to many mapping

Use one to mapping to create 1…N relationship between entities or objects.

We have to write two entities i.e. Company and Branch such that multiple branch can be associated with a single company, but one single branch can not be shared between two or more company.

Hibernate one to many mapping solutions:

  1. One to many mapping with foreign key association
  2. One to many mapping with join table

This problem can be solved in two different ways.

One is to have a foreign key column in branch table i.e. company_id. This column will refer to primary key of Company table. This way no two branch can be associated with multiple company.

Second approach is to have a common join table let’s say Company_Branch, This table will have two column i.e. company_id which will be foreign key referring to primary key in Company table and similarly branch_id which will be foreign key referring to primary key of Branch table.

If @OneToMany/@ManyToOne doesn’t have a mirroring @ManyToOne/@OneToMany association respectively on the child side then, the @OneToMany/@ManyToOne association is unidirectional.

@OneToMany Unidirectional Relationship

In this approach, any one entity will be responsible for making the relationship and maintaining it. Either Company declare the relationship as one to many, Or Branch declare the relationship from its end as many to one.

CASE 1: (Mapping with foreign key association)

If we use only @OneToMany then there will be 3 tables. Such as company, branch and company_branch.

NOTE:
In the above example I have used terms like cascade, orphanRemoval, fetch and targetEntity, which I will explain in my next post.

Table company_branch will have two foreign key company_id and branch_id.

Now, if we persist one Company and two Branch(s):

Hibernate is going to execute the following SQL statements:

  • The @OneToMany association is by definition a parent(non-owning) association, even if it’s a unidirectional or a bidirectional one. Only the parent side of an association makes sense to cascade its entity state transitions to children.
  • When persisting the Company entity, the cascade will propagate the persist operation to the underlying Branch children as well. Upon removing a Branch from the branchs collection, the association row is deleted from the link table, and the orphanRemoval attribute will trigger a Branch removal as well.

The unidirectional associations are not very efficient when it comes to removing child entities. In this particular example, upon flushing the persistence context, Hibernate deletes all database child entries and reinserts the ones that are still found in the in-memory persistence context.

On the other hand, a bidirectional @OneToMany association is much more efficient because the child entity controls the association.

CASE 2: (Mapping with foreign key association)

If we use only @ManyToOne then there will be 2 tables. Such as company, branch.

This above code will generate 2 tables Company(company_id,name) and Branch(branch_id,name,company_company_id). Here Branch is the owning side since it has the foreign key association.

CASE 3: (Mapping with foreign key association)

If we use both @ManyToOne and @OneToMany then it will create 3 tables Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)

This below mapping might look like bi-directional but it’s not. It defines not one bi-directional relation, but two separate uni-directional relations.

CASE 4: (Unidirectional @OneToMany with @JoinColumn)(Mapping with foreign key association)

If we use @OneToMany with @JoinColumn then there will be 2 tables. Such as company, branch

In the above entity inside @JoinColumn, name refers to foreign key column name which is companyId i.e company_id and rferencedColumnName indicates the primary key i.e id of the entity(Company) to which the foreign key companyId refers.

CASE 5: (Mapping with foreign key association)

If we use @ManyToOne with @JoinColumn then there will be 2 tables. Such as company,branch.

The @JoinColumn annotation helps Hibernate to figure out that there is a company_id Foreign Key column in the branch table that defines this association.

Branch will have the foreign key company_id, so it’s the owner side.

Now, if we persist 1 Company and 2 Branch(s):

when removing the firsts entry from the child collection:

company.getBranches().remove(0);

Hibernate executes two statements instead of one :

  • First it will make foreign key field to null(to break association with parent) then it will delete the record.
Update branch set branch_id = null where where id = 1 delete from branch where  id = 1 ;

CASE 6: (Mapping with join table)

Now, let’s consider one-to-many relation where Person(id,name) get associated with multiple Veichle(id,name,number) and multiple vehicles can belongs to same person.

One day while on highway petrol sheriff Carlos found few abandoned vehicles, most probably stolen one. Now sheriff has to update the vehicles details(number,name) in their database but the main problem is, there is no owner for those vehicles, so person_id(foreign key )field will remain null.

Now sheriff saves two stolen vehicle to db as follows:

Vehicle vehicle1 = new Vehicle("ford", 1);   
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);

Hibernate is going to execute the following SQL statements:

insert into vehicle (id, name, person_id) values (1, "ford", null); insert into vehicle (id, name, person_id) values (2, "gmc", null);id   |name|person_id|
-----|----|---------|
1 |ford|NULL |
2 |benz|NULL |
---------------------

This above strategy will force us to put null values in the column to handle optional relationships.

Typically, we think of many-to-many relationships when we consider a join table, but, using a join table, in this case, can help us to eliminate these null values:

This approach uses a join table to store the associations between Branch and Company entities. @JoinTable annotation has been used to make this association.

In this example we have applied @JoinTable on Vehicle side(Many Side).

Lets see how the database schema will look like:

Above code will generate 3 tables such as person(id,name), vehicle(id,name) and vehicle_person(vehicle_id,person_id). Here vehicle_person will hold the foreign key relation to both Person and Vehicle entities.

So when sheriff save vehicle details, no null value have to get persisted to vehicle table because we are keeping foreign key association in vehicle_person tables not in vehicle table.

Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);

Hibernate is going to execute the following SQL statements:

insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id   |name|
-----|----|
1 |ford|
2 |benz|
-----------

This above example shows, how we got rid of null value insertion.

@OneToMany Bi-directional Relationship

  • The bidirectional @OneToMany association also requires a @ManyToOne association on the child side. Although the Domain Model exposes two sides to navigate this association, behind the scenes, the relational database has only one foreign key for this relationship.
  • Every bidirectional association must have one owning side only (the child side), the other one being referred to as the inverse (or themappedBy) side.
  • If we use the @OneToMany with the mappedBy attribute set, then we have a bidirectional association, meaning we need to have a @ManyToOne association on the child side which the mappedBy references.
  • The mappedBy element defines a bidirectional relationship. This attribute allows you to refer the associated entities from both sides.

The best way to map a @OneToMany association is to rely on the @ManyToOne side to propagate all entity state changes.

If we persist 2 Branch(s)

Hibernate generates just one SQL statement for each persisted Branch entity:

insert into company (name, id) values ("company1",1); 
insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);

If we remove a Branch:

Company company = entityManager.find( Company.class, 1L );   Branch branch = company.getBranches().get(0);   company.removeBranches(branch);

There’s only one delete SQL statement that gets executed:

delete from Branch where id = 1

So, the bidirectional @OneToMany association is the best way to map a one-to-many database relationship when we really need the collection on the parent side of the association.

@JoinColumn Specifies a column for joining an entity association or element collection. The annotation @JoinColumn indicates that this entity is the owner of the relationship. That is the corresponding table has a column with a foreign key to the referenced table.

In the above example, the owner Entity Branch, has a Join Column named company_id that has a foreign key to the non-owner Company entity.

Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times. The addBranches() and removeBranches() are utilities methods that synchronize both ends whenever a child element(i.e Branch) is added or removed.

Unlike the unidirectional @OneToMany, the bidirectional association is much more efficient when managing the collection persistence state.

Every element removal only requires a single update (in which the foreign key column is set to NULL) and if the child entity life cycle is bound to its owning parent so that the child cannot exist without its parent, then we can annotate the association with the orphan-removal attribute and disassociating the child will trigger a delete statement on the actual child table row as well.

NOTE:

  • In the above bi-directional example the term Parent and Child in Entity/OO/Model( i.e in java class) refers to Non-woning/Inverse and Owning side in SQL respectively.

In java point of view Company is the Parent and branch is the child here. Since a branch can not exist without parent.

In SQL point of view Branch is the Owner side and Company is the Non-woning(Inverse) side. Since there is 1 company for N branches, each branch contains a foreign key to the Company it belongs.This means branch “owns” (or literally contains) the connection (information). This is exactly the opposite from the OO/model world.

@OneToMany Bi-directional Relationship(Mapping with join table)

From CASE 6 we have Unidirectional mapping with @JoinTable, so if we add mappedBy attribute to Person entity then the relationship will become bi-directional.

SUMMARY

There are several things to note on the aforementioned mapping:

  • The @ManyToOne association uses FetchType.LAZY because, otherwise, we’d fall back to EAGER fetching which is bad for performance.
  • The bidirectional associations should always be updated on both sides, therefore the Parent side should contain the addChild and removeChild combo(Parent side Company contains two utility methods addBranches and removeBranches). These methods ensure we always synchronize both sides of the association, to avoid object or relational data corruption issues.
  • The child entity, Branch, implement the equals and hashCode methods. Since we cannot rely on a natural identifier for equality checks, we need to use the entity identifier instead. However, we need to do it properly so that equality is consistent across all entity state transitions. Because we rely on equality for the removeBranches, it’s good practice to override equals and hashCode for the child entity in a bidirectional association.
  • The @OneToMany association is by definition a parent association, even if it’s a unidirectional or a bidirectional one. Only the parent side of an association makes sense to cascade its entity state transitions to children.

Citations:

[1]: Red Hat Hibernate Documentation
[2]: Hibernate Documentation
[3]: High-Performance Java Persistence Book by vlad mihalcea

l believe you must have gained something from my article and I will be glad if you can share your thoughts with me.

Applaud and follow me if you like this article!

Questions? Comments?

--

--