Most efficient way to map a @OneToMany relationship with JPA and Hibernate
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. ThemappedBy
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 themappedBy
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
andBranch
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:
- One to many mapping with foreign key association
- 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 similarlybranch_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 aBranch
from the branchs collection, the association row is deleted from the link table, and theorphanRemoval
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 the
mappedBy
) side. - If we use the
@OneToMany
with themappedBy
attribute set, then we have a bidirectional association, meaning we need to have a@ManyToOne
association on the child side which themappedBy
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()
andremoveBranches()
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 usesFetchType.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
andremoveChild
combo(Parent side Company contains two utility methodsaddBranches
andremoveBranches
). 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!