โ ๏ธ Warning: This post is over a year old, the information may be out of date.
๐ One To Many - Many To One (JPA and Hibernate)
๐ | โฐ 5 minutes
ToC
Intro
Nowadays, programming evolve and growth rapidly from time to time but most of problem and issue that we want to solve have same design concept in many ways.
Today, I want to wrote something about relational database system. The topic today are about one-to-many
/ many-to-one
association relationship. It about a links of multiple tables based on a FK (Foreign Key) column which that the child table record references back to the PK (Primary Key) of the parent table row data. I also will touch unidirectional
and bidirectional
propagation.
I will explain everything using Springboot
concept which mean I use Java language and JPA (Java Persistence API) + Hibernate annotation, to show how it work and how we can design our database table easier. Yes, it easier because you just need to code the table design and it will create for us but to get efficient database structure and efficient SQL statements is definitely not a trivial thing to do
Database design
Let’s create two entities model based on our commons “e-commerce” design idea since everyone understand very well about it nowadays. We will create customer
and cart
table and explain how we can connect both tables and explain the connection in details.
Unidirectional implementation
The unidirectional approach is simple and easy to understand. With @OneToMany
annotation on choosen parent table will defines the relationship. Take look my example below:
Model
Lets create 2 entities from our domain which is customer
and cart
Table - Customer
package io.robbinespu.demo.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "Customer")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long id;
private String name;
@OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Cart> order = new ArrayList<>();
}
Table - Cart
package io.robbinespu.demo.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "Cart")
public class Cart implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long id;
private String item;
}
Details and explaination
With this two entities designed (source code are avilable here ), JPA persistance actually will creating 3 table for us when executed. Feeling weird huh? If you show the design to a DBA, s/he may assume it more like a many-to-many database association than a one-to-many relationship.
Since we now have three tables, it means data transaction are using more storage than necessary. It also means it use more memory to cache index of this asscociation and more time need for extra query when inserting and deleting rows. Plus, instead of just having only one FK, we now have two of them and need to be take care during any data CRUD operations.
The work around it to use @JoinColumn
annotation inside our parent table, on top of our association
diff --git a/src/main/java/io/robbinespu/demo/model/Customer.java b/src/main/java/io/robbinespu/demo/model/Customer.java
index d122ca1..d67aba8 100644
--- a/src/main/java/io/robbinespu/demo/model/Customer.java
+++ b/src/main/java/io/robbinespu/demo/model/Customer.java
@@ -8,6 +8,7 @@ import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
+import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@@ -26,6 +27,7 @@ public class Customer implements Serializable {
cascade = CascadeType.ALL,
orphanRemoval = true
)
+ @JoinColumn(name = "order")
private List<Cart> order = new ArrayList<>();
If you don’t know how to read a unified “git diff”, you may check PR (pull request) files changes on my source code repository as split view.
The @JoinColumn
annotation will help hibernate to specifies a column for joining an entity association or element collection.
Now our datatabase table design look much cleaner and much easier to understand then before. Please take note, unidirectional implementation only modify parent entity. If you glance the child entity, you won’t notice it has association with other entity.
Bidirectional implementation
As previously mentioned, is easier because you need to do changes on parent entity only but for bidirectional association it require child entity mapping to it parent entity using @ManyToOne
annotation to controlling the association.
In others word, bidirectional implementation will modify both parent and child entities. Take look on example (code snippet) below:
Model
Table - Customer
package io.robbinespu.demo.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "Customer")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long id;
private String name;
@OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true,
mappedBy = "customer"
)
private List<Cart> order = new ArrayList<>();
}
Table - Order
package io.robbinespu.demo.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "Cart")
public class Cart implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long id;
private String item;
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
}
Details and explaination
Now, did you notice the different? On parent entity which is Customer
, I don’t use @JoinColumn(name = "order")
like unidirectional and I added one more parameter inside @OneToMany
field which are mappedBy = "customer"
, take note it use it own entity name. This is to let JPA know that the parent entity have child entity that use parent PK and child FK references.
Our ER diagram for bidirectional will look same like unidirectional + @JoinColumn
annotation. So it mean, DBA will understand what relationship that this table have.
But our entity code are much readable and easier to understand then before. This approach will synchronize both sides of entities.
Side note
To be honest and additional note, itโs good practice to override equals and hashCode for the child entity in a bidirectional association. You may wrote something like this:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Cart )) return false;
return id != null && id.equals(((Cart) o).getId());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
It hard to express what is the reason I said so. Let take it as object equality things are a little bit more complicated, just play safe with some dirty checking.
Conclusion
From the experiment, software engineer should understand that they have option to wrote entity relationship either using unidirectional or bidirectional approach. Our cases maybe different but I would use bidirectional since it much clean eventhough it add more LOC (lines of code) on my code. The DBA and documentation people will get the idea of our tables relationship since the ERD are much better represented.
Posted by: Hugo