Spring Boot Course
JPA Relationship Mapping
When mapping entities with each other, we describe the relation among entities using OneToOne, OneToMany,ManyToOne or ManyToMany mappings.
OneToOne
It's a relationship where a record in one entity(table) is associated with exactly one record in another entity(table).
For example, A Person has a PAN (Card) is a perfect example of One To One association.
Unidirectional - Person can refer to PAN entity
Bidirectional - PAN entity can refer back to Person
@Entity public class Person { @Id private int id; @OneToOne @JoinColumn(name="PAN_ID") private PAN pan; // ... } @Entity public class PAN { @Id private int id; @OneToOne(mappedBy="pan") private Person person; // ... }
Here @JoinColumn annotation is used to specify the foreign key column relationship details between 2 entities. name defines the name of the foreign column.
Some more examples like User - Email, User - Address Country - Capital etc.
OneToMany
A OneToMany relationship refers to the relationship between two entities/tables A and B in which one element/row of A may only be linked to many elements/rows of B, but a member of B is linked to only one element/row of A.
The opposite of OneToMany is ManyToOne relationship.
For example, A Person has many Skill(s), But a skill can not be shared among Person(s). A Skill can belong to utmost One Person.
One more example could be relationship between Employee and Department where an Department is associated with Collection of Employee(s)
Unidirectional - A Department can directly reference Employee(s) by collection
Bidirectional - Each Employee has a reference back to Department
Some more examples are CLASS - Students, Book - Pages etc.
@Entity public class Employee { @Id private int id; @ManyToOne @JoinColumn(name="DEPT_ID") private Department department; // ... } @Entity public class Department { @Id private int id; @OneToMany(mappedBy="department") private Collection<Employee> employees; // ... }
-
A many-to-one relationship between two entities is defined by using @ManyToOne Annotation. The child entity, that has the join column , is called the owner of the relationship. The @JoinColumn is used to specify the foreign key column details.
-
A one-to-many relationship between two entities is defined by using @OneToMany Annotation. mappedBy element indicates the entity that owns the bidirectional relationship. Usually, the child entity is one that owns the relationship and the parent entity contains the @OneToMany annotation.
ManyToMany
A many-to-many relationship refers to relationship between two entities/tables A and B in which one element/row of A are associated with many elements/rows of B and viceversa.
For example, One Person Has Many Skills, a Skill is reused between Person(s).
One more example of this could be relationship between Employee and Project. Each employee can work on multiple Project(s) and each Project can be worked upon by multiple Employee(s). One more example could be relationship between Customer(s) and Product(s) where One or More Customer(s) purchase many different Product(s) and Product(s) can be purchased by different Customer(s)
Unidirectional - A Project can directly reference its Employee(s) by collection
Bidirectional - An Employee has Collection of Projects that it relates to.
Some more examples are COURSES - STUDENTS, ORDERS - PRODUCTS etc.
@Entity public class Employee { @Id private int id; @ManyToMany private Collection<Project> projects; // ... } @Entity public class Project { @Id private int id; @ManyToMany(mappedBy="projects") private Collection<Employee> employees; // ... }
Association or junction table is must to implement a ManyToMany relationship, this separate table connects one line from Employee to one line from Poject using foreign keys. And each primary key of Employee and Project can be copied over multiple times to this table.
How do we implement ManyToMany mappings with the self entity in JPA?
We need to maintain two different mappings in the same entity for ManyToMany relationship as shown below
@ManyToMany @JoinTable(name="table_friends", joinColumns=@JoinColumn(name="personId"), inverseJoinColumns=@JoinColumn(name="friendId")) private Set<User> friends; @ManyToMany @JoinTable(name="table_friends", joinColumns=@JoinColumn(name="friendId"), inverseJoinColumns=@JoinColumn(name="personId")) private Set<User> friendOf;
In the above Bidirectional Mapping, One side of relationship will maintain the User's list of friends (friends), and the inverse side of relationship will maintain how many people have this User in their friend list (friendOf).
The Inverse side of the relationship can also be described as
@ManyToMany(mappedBy="friends") private Set<User> friendOf = new HashSet<User>();
@JoinColumn vs mappedBy
- mappedBy attribute is used to tell hibernate which entity is the owner side of the relation. This is used mostly in OnetoMany and ManytoOne relationship. This is generally used for bidirectional relationship.
- @JoinColumn is used for joining the columns and act as foreign key.
JPA Fetch
Based on fetch configurations that developer has done, JPA allows entities to load the objects with which they have a relationship.
We can declare the fetch value in the @OneToOne, @OneToMany, @ManyToOne, @ManyToMany annotations. These annotations have an attribute called fetch that serves to indicate the type of the fetch we want to perform. It have two valid values FetchType.EAGER and FetchType.LAZY.
With LAZY configuration, we are telling JPA that we want to lazily load the relation entities, so when retrieving an entity, its relations will not be loaded until unless we try refer the related entity using getter method. On the Contrary, with EAGER it will load it's relation entities as well.
By default all ToMany relationships are LAZY, while ToOne relationships are EAGER.
Cascade Types
JPA allows us to propogate entity state changes from Parents to Child entities. This concept is called Cascading in JPA.
The cascade configuration option accepts an array of CascadeTypes.
The cascade types supported by JPA are
- CascadeType.PERSIST : means save() or persist() operations cascade to related entities.
- CascadeType.MERGE : means that related entities are merged when the owning entity is merged.
- CascadeType.REFRESH : means the child entity also gets reloaded from the database whenever the parent entity is refreshed.
- CascadeType.REMOVE : means propogates the remove operation from parent to child entity.
- CascadeType.DETACH : means detach all child entities if a
manual detachoccurs for parent. - CascadeType.ALL : is shorthand for all of the above cascade operations.
There is no default cascade type in JPA. By default, no operation is cascaded.
Lets see an ecommerce application using all these JPA relationships
User
package com.praveen.ecommerce.entity; import java.util.List; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.*; @AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @Entity @Table(name="users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="user_id") private Long id; private String name; private String email; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "profile_id") private UserProfile profile; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Order> orders; }
UserProfile
package com.praveen.ecommerce.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.*; @AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @Entity @Table(name="user_profile") public class UserProfile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="profile_id") private Long id; private String address; private String phoneNumber; @OneToOne(mappedBy = "profile") private User user; }
Product
package com.praveen.ecommerce.entity; import java.util.List; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.Table; import lombok.*; @AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @Entity @Table(name="products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="product_id") private Long id; private String name; private double price; @ManyToMany(mappedBy = "products") private List<Order> orders; @ManyToMany @JoinTable( name = "product_category", joinColumns = @JoinColumn(name = "product_id"), inverseJoinColumns = @JoinColumn(name = "category_id")) private List<Category> categories; }
Category
package com.praveen.ecommerce.entity; import java.util.List; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToMany; import jakarta.persistence.Table; import lombok.*; @AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @Entity @Table(name="categories") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="category_id") private Long id; private String name; @ManyToMany(mappedBy = "categories") private List<Product> products; }
Order
package com.praveen.ecommerce.entity; import java.util.Date; import java.util.List; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.*; @AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @Entity @Table(name="orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="order_id") private Long id; private Date orderDate; @ManyToOne @JoinColumn(name = "user_id") private User user; @ManyToMany @JoinTable( name = "order_product", joinColumns = @JoinColumn(name = "order_id"), inverseJoinColumns = @JoinColumn(name = "product_id")) private List<Product> products; }