Object relational mapping with SpringBoot

Hibernate will privide object relational mapping those are:

  1. 1. OneToOne Mapping
  2. 1. OneToMany Mapping
  3. 1. ManyToOne Mapping
  4. 1. ManyToMany Mapping
Before we learn about these mappings lets learn about data fetch types those are:
  1. 1. Lazy: Load the associated data of the other entity, only when requested. This is done on demand.
  2. 2. Eager: Load the associated data of the other entity, beforehand which is bit costly.
There are specified fetching types for each relationship type which is applied by Hibernate by default.
  1. 1. OneToOne: EAGER
  2. 2. OneToMany: LAZY
  3. 3. ManyToOne: EAGER
  4. 4. ManyToMany: LAZY
Example:
If we have a relationship between university and student, when university data is fetched, we don’t want to fetch students right? Because, one university will have thousands of students in the students array in the mapping. It will be a very costly operation. So, we can tell hibernate to keep it with LAZY initialization.

Let's, learn about the Cascade Types in Hibernate

In Hibernate, Cascade Types mean how the data should be kept between the two related entities. There are set of pre defined types.

  1. 1. CascadeType.PERSIST : Both save() or persist() operations cascade to related entities.
  2. 2. CascadeType.MERGE : Related entities are merged when the ownership entity is merged.
  3. 3. CascadeType.REFRESH : Does same thing for the refresh() operation.
  4. 4. CascadeType.REMOVE : Removes all related entities association with this setting when the ownership entity is deleted.
  5. 5. CascadeType.DETACH : Detaches all related entities if a “manual detach” occurs.
  6. CascadeType.ALL : All of the above cascade operations.
There is no default cascade type in JPA. By default, no operation is cascaded. If we want, we can use several cascade types at once also.

One To One Mapping

I’m going to build up a relationship between Customer and Address. Any Customer has an address here. Two Customers cannot have the same Address!

I don’t want the Address record, without the relevant Customer. I think it can be the most common scenario. In a one to one mapping, both entities are tightly coupled. After the Customer is removed, we cannot use his/her Address. So I will define CascadeType as ALL(If you want to keep the Address, change it to PERSIST). Then address won’t be deleted even we delete the Customer. Since Hibernate decides FetchType for one to one mapping is EAGER by default, I don’t want to mention it as a rule.

Normally we record child entity primary key as the foreign key of the owner entity. So User should have a column in the table to record the address ID. I have given its name as “address_id” and its referenced by “id” column in Address entity.

CustomerModel.java

                        package com.sb.orm.sb.model;

                        import com.sb.orm.sb.enums.CustomerStatus;
                        import jakarta.persistence.*;
                        
                        import lombok.AllArgsConstructor;
                        import lombok.Getter;
                        import lombok.NoArgsConstructor;
                        import lombok.Setter;
                        import lombok.EqualsAndHashCode;
                        import lombok.Builder;
                        import lombok.ToString;
                        import org.hibernate.annotations.CreationTimestamp;
                        import org.hibernate.annotations.UpdateTimestamp;
                        
                        import java.io.Serializable;
                        import java.time.LocalDateTime;
                        
                        @Getter
                        @Setter
                        @NoArgsConstructor
                        @AllArgsConstructor
                        @ToString
                        @EqualsAndHashCode
                        @Builder(toBuilder = true)
                        @Entity
                        @Table(name = "customer_profile")
                        public class CustomerModel implements Serializable {
                        
                            @Id
                            @GeneratedValue(strategy = GenerationType.SEQUENCE)
                            @Column(name = "id")
                            private Long id;
                        
                            @Column(name = "customer_name")
                            private String customerName;
                        
                            @Column(name = "password")
                            private String customerPassword;
                        
                            @Column(name = "customer_age")
                            private int customerAge;
                        
                            @Column(name = "customer_mobile_number")
                            private String customerMobileNumber;
                        
                            @Column(name = "customer_email_address")
                            private String customerEmailAddress;
                        
                            // OneToOne Mapping
                            @OneToOne(cascade = CascadeType.ALL)
                            @JoinColumn(name = "address_id", referencedColumnName = "id")
                            private AddressModel address;
                        
                            @Column(name = "status")
                            private CustomerStatus status;
                        
                            @Column(name = "otp")
                            private String customerOtp;
                        
                            @Column(name = "verified")
                            private boolean verified;
                        
                            @Column(name = "created_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @CreationTimestamp
                            private LocalDateTime createdDate;
                        
                            @Column(name = "updated_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @UpdateTimestamp
                            private LocalDateTime updatedDate;
                        }                        
                    

AddressModel.java

                        package com.sb.orm.sb.model;

                        import jakarta.persistence.*;
                        import lombok.*;
                        import org.hibernate.annotations.CreationTimestamp;
                        import org.hibernate.annotations.UpdateTimestamp;

                        import java.time.LocalDateTime;

                        @Getter
                        @Setter
                        @NoArgsConstructor
                        @AllArgsConstructor
                        @ToString
                        @EqualsAndHashCode
                        @Builder(toBuilder = true)
                        @Entity
                        @Table(name = "customer_address")
                        public class AddressModel {

                            @Id
                            @GeneratedValue(strategy = GenerationType.IDENTITY)
                            @Column(name = "id")
                            private long id;

                            @Column(name = "doorNumber")
                            private String doorNumber;

                            @Column(name = "street")
                            private String street;

                            @Column(name = "landMark")
                            private String landMark;

                            @Column(name = "city")
                            private String city;

                            @Column(name = "state")
                            private String state;

                            @Column(name = "country")
                            private String country;

                            @Column(name = "pinCode")
                            private String pinCode;

                            @OneToOne(mappedBy = "address")
                            private CustomerModel customer;

                            @Column(name = "created_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @CreationTimestamp
                            private LocalDateTime createdDate;

                            @Column(name = "updated_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @UpdateTimestamp
                            private LocalDateTime updatedDate;
                        }
                       
                    

If you see above two model classes, in CustomerModel.java I have added the AddressModel as fields and annotated this field as @OneToOne mapping along with the cascade type as ALL, because if customer deleted I also want to delete the address. Next, I annotated with @JoinColumn with the name as address_id and reference type as id it mean here we're creating the foreign key relation.

Inside the CustomerAddress.java we have added the CustomerModel as a field and annotated this field with @OneToOne mapping using This way we can have a Bi directional one-to-one mapping!

OneToOne mapping example full source code is available in GitHub Repository: OneToOne Mapping Example


OneToMany Mapping

I’m creating another relationship between Post and Comment entities. Any kind of post can have one or more comments. But every comment is having only one post! So, this is a one-to-many relationship exactly.

I need to see the comments of each post, while post data is fetched. That means, comments should be eagerly fetched right? That’s why I have put fetch type as EAGER or comments set. Hibernate will set fetch type as LAZY by default for one to many mappings. To, override that, I have specifically mentioned the fetch type as EAGER.

Let's see with an example: Before we fetch comment and post data lets create the data first.

PostModel.java

                        package com.sb.orm.sb.model;

                        import jakarta.persistence.*;
                        import lombok.*;
                        import org.hibernate.annotations.Comment;
                        import org.hibernate.annotations.CreationTimestamp;
                        import org.hibernate.annotations.UpdateTimestamp;

                        import java.time.LocalDateTime;
                        import java.util.HashSet;
                        import java.util.Set;

                        @Getter
                        @Setter
                        @NoArgsConstructor
                        @AllArgsConstructor
                        @ToString
                        @EqualsAndHashCode
                        @Builder(toBuilder = true)
                        @Entity
                        @Table(name = "posts")
                        public class PostModel {

                            @Id
                            @GeneratedValue(strategy = GenerationType.SEQUENCE)
                            @Column(name = "id")
                            private long id;

                            @Column(name = "title")
                            private String title;

                            @Column(name = "description")
                            private String description;

                            @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
                            @JoinColumn(name = "post_id", referencedColumnName = "id")
                            private Set<CommentModel> comment = new HashSet<>();

                            @Column(name = "created_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @CreationTimestamp
                            private LocalDateTime createdDate;

                            @Column(name = "updated_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @UpdateTimestamp
                            private LocalDateTime updatedDate;
                        }
                    

CommentModel.java

                        package com.sb.orm.sb.model;

                        import jakarta.persistence.*;
                        import lombok.*;
                        import org.hibernate.annotations.CreationTimestamp;
                        import org.hibernate.annotations.UpdateTimestamp;

                        import java.time.LocalDateTime;

                        @Getter
                        @Setter
                        @NoArgsConstructor
                        @AllArgsConstructor
                        @ToString
                        @EqualsAndHashCode
                        @Entity
                        @Table(name = "comments")
                        @Builder(toBuilder = true)
                        public class CommentModel {

                            @Id
                            @GeneratedValue(strategy = GenerationType.SEQUENCE)
                            @Column(name = "id")
                            private long id;

                            @Column(name = "post_id")
                            private Long postId;

                            @Column(name = "comment")
                            private String comment;

                            @Column(name = "created_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @CreationTimestamp
                            private LocalDateTime createdDate;

                            @Column(name = "updated_by")
                            @Temporal(TemporalType.TIMESTAMP)
                            @UpdateTimestamp
                            private LocalDateTime updatedDate;
                        }
                    

Comment is having the owning entity primary key as described before.

This way we can have a Uni directional one-to-one mapping! Actually in one to many scenarios, it is enough to have uni directional relationship. No need to be bi directional. We just need to make sure that the primary key of the Post should be inserted into Comment object while a comment is saved.

OneToMany mapping example full source code is available in GitHub Repository: OneToMany Mapping Example


ManyToMany Mapping

ManyToMany mapping is a very special scenario, where we need an additional table more than the two entities.

Let me take an example and explain. In a social media on daily basis will do multiple post by tagging the candidates, every Post has one or more Tags. Any Post can have one or more Tags and Any Tagg can have one or more Post! Then that is many to many right?

Lets see below Example:
PostModel.java

                        @Entity
                        @Table(name = "posts")
                        @Getter
                        @Setter
                        @NoArgsConstructor
                        @AllArgsConstructor
                        @EqualsAndHashCode
                        @Builder(toBuilder = true)
                        public class PostModel {

                            @Id
                            @GeneratedValue(strategy = GenerationType.IDENTITY)
                            @Column(name = "id")
                            private Long id;

                            @Column(name = "title")
                            private String title;

                            @Column(name = "description")
                            private String description;

                            @ManyToMany(fetch = FetchType.LAZY,
                                    cascade = {
                                            CascadeType.PERSIST,
                                            CascadeType.MERGE
                                    })
                            @JoinTable(name = "post_tags",
                                    joinColumns = {@JoinColumn(name = "post_id")},
                                    inverseJoinColumns = {@JoinColumn(name = "tag_id")})
                            private Set<TagModel> tags = new HashSet<>();

                        }
                    

Post is the owning entity in the relationship. So, it’s annotated with “@ManyToMany”. We have to place join table config here. So, “@JoinTable” annotation represents this new table with name as “post_id”. There we have to give the two columns we use for the associations. They are primary keys in

Post

and Tag tables.

I need to show the Tag when Post data is retrieved. Since hibernate considers LAZY as the default fetch type for ManyToMany mappings, I had to setup EAGER fetch there.

TagModel.java

                        @Entity
                        @Table(name = "tags")
                        @Getter
                        @Setter
                        @NoArgsConstructor
                        @AllArgsConstructor
                        @EqualsAndHashCode
                        @Builder(toBuilder = true)
                        public class TagModel {

                            @Id
                            @GeneratedValue(strategy = GenerationType.IDENTITY)
                            @Column(name = "id")
                            private Long id;

                            @Column(name = "name")
                            private String tagName;

                            @ManyToMany(fetch = FetchType.LAZY,
                                    cascade = {
                                            CascadeType.PERSIST,
                                            CascadeType.MERGE
                                    },
                                    mappedBy = "tags")
                            @JsonIgnore        
                            private Set<PostModel> posts = HashSet<>();
                        }
                    

In the child entity, we just need to link the name of the property mapped in Post entity.

“@JsonIgnore” annotation was placed there for Post property since I do not need to have the Post object to be seen in Tag data.

This way we can establish a Bi directional one-to-one mapping!

Data Stored in Third table

Postman call

ManyToMany mapping example full source code is available in GitHub Repository: ManyToMany Mapping Example