📢 Actions Speak Louder Than Words!

One To Many - Many To One (JPA and Hibernate)

Posted: Jun 7, 2021 | Reading time: 6 min
Click to view 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.

Edit

Discussion and feedback

You can use utterances provided below to post comment on behalf using Github account. Alternatively, you can just send a public comment to my mailing list or send a private message to my e-mail. In a few cases and on certain time, I just don’t have time to moderate them. Please read terms-of-service (ToS) for details.