Hibernate Relations, Cascade Types, Fetch Types and Orphan Removal
Associations, Cascade Types, Fetch Types and Orphan Removal
Greetings, everyone; today, we will discuss the Hibernate Relations, Cascade Types, Fetch Types and Orphan Removal. I am going to publish a couple of articles about Hibernate. This is the second one. Let’s refresh our coffees and get started! ☕ 📰😊
Hibernate Associations
You can ask why we need associations in programming. Well, the simple answer to this is that we associate with many things in this world. We cannot be without them. They cannot be without us. Everything moves in a symphony of interconnectedness. It is like an orchestra that is playing a big harmony.
In the business world, we have relations and hierarchy with other teams as developers. Each team depends on another team.
It is the same idea in programming: our entities must depend on others. For instance, the Product must have Images, Categories, Details and more.
In the programming (Hibernate/JPA) world, we have one-to-one, one-to-many, many-to-many and many-to-many relation types.
One-To-One
one-to-one relation means that an entity is associated with only one entity. For instance, we have a product entity that is related to a brand entity. Obviously, each product belongs to only one brand. Therefore, the one-to-one relation would be preferred in this case.
Product Entity
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@Column(name = "quantity")
private Integer quantity;
@OneToOne
@JoinColumn(name = "product_detail_id")
private ProductDetail productDetail;
}
ProductDetail Entity
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class ProductDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "description", length = 1000)
private String description;
@Column(name = "warranty", columnDefinition = "boolean default true")
private Boolean warranty;
@OneToOne(mappedBy = "productDetail")
private Product product;
}
Many-To-One and One-To-Many
many-to-one relation means that many entities are associated with one another entity. For instance, we have a Product enrolled with only one Color. However, the Color can be enrolled with many Products.
The Product entity is associated with multiple Images, while each Image corresponds to exactly one product. This relationship is expressed as a One-to-Many association.
Product Entity
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@Column(name = "quantity")
private Integer quantity;
@OneToOne
@JoinColumn(name = "product_detail_id")
private ProductDetail productDetail;
@ManyToOne
@JoinColumn(name = "color_id")
private Color color;
}
Color Entity
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Color {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@OneToMany(name="color")
private List<Product> products;
}
Many-To-Many
Two entities are connected in a many-to-many relationship, meaning each entity can be associated with multiple instances of the other. For instance, Each Product might have many tags, and each Tag might have many Products.
FIRST OPTION, Many-To-Many
Create all entities that are connected to each other in the Hibernate App.
Product
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@Column(name = "quantity")
private Integer quantity;
@OneToMany(mappedBy = "product")
private List<ProductTag> productTags;
}
Tag
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@OneToMany(mappedBy = "tag")
private List<ProductTag> productTags;
}
ProductTag
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class ProductTag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
@ManyToOne
@JoinColumn(name = "tag_id")
private Tag tag;
}
The Second Option with an Inverse Join Column, Many-to-Many
In JPA, when we are dealing with Many-to-Many associations between entities, we often use a join table to represent the relationship. This join table typically consists of foreign keys that refer to the primary keys of the associated entities.
For the Product entity, we have a many-to-many relationship with Tag entities, which is represented by the tags field. To specify the details of the join table, we use the @JoinTable annotation. In the @JoinTable annotation, we have two properties related to foreign keys: joinColumns and inverseJoinColumns.
joinColumns: This property defines the foreign key column in the join table that references the primary key of the owning entity. It specifies the columns in the join table that represent the current entity Product.
inverseJoinColumns: This property defines the foreign key column in the join table that references the primary key of the target entity. It specifies the columns in the join table that represent the associated entity.
Product
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@Column(name = "quantity")
private Integer quantity;
@OneToOne
@JoinColumn(name = "product_detail_id")
private ProductDetail productDetail;
@ManyToOne
@JoinColumn(name = "color_id")
private Color color;
@ManyToMany
@JoinTable(
name = "product_tag",
joinColumns = @JoinColumn(name = "product_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")
)
private List<Tag> tags;
}
Fetch Types
Fetch type is a strategy for retrieving data from the database.
Lazy Fetch Type
The lazy fetch type is managed using proxies by Hibernate. Hibernate creates a proxy for each entity. The benefit is retrieving relation data (entity) when it is called. It enhances performance. It is a valuable strategy in one-to-many, many-to-many relations if they have massive data.
Eager Fetch Type
The eager fetch type retrieves data directly from the database and maps the actual entity without using any additional proxy. It might kill the performance of one-to-many or many-to-many relations. On the other hand, the eager fetch type in one-to-one relations is significantly helpful. Default is Eager in one-to-one and many-to-one.
CASCADE TYPES
Cascade types are responsible for ensuring that an entity instance relies on another entity instance. An entity depends on another entity. For instance, a Product will be pointless without Product Details. Cascade Types are used when a Product is created then Product Details must be created simultaneously, or a Product is deleted, then Product Details must be deleted simultaneously.
CascasdeType.ALL
It admits to performing all operations on related entities. For instance, when a persistent operation is performed on a Product entity, it will affect other related entities that have been established within the Product. (Delete, Update, Create, etc.)
CascasdeType.PERSIST
Obviously, this type is understandable in its name. It admits to performing a persistent operation on the related entity, such as create. When a Product entity is created, Product Details will be created before the creation of the Product. Why? Because the Ğroduct entity table involves product_detail_id, clearly without Product Detail ID, Hibernate is not able to save the product. Therefore, the Product Detail must be created before the Product entity. (persist operations)
CascasdeType.MERGE
CascadeType.MERGE specifies that when a merge operation is performed on an entity, the same operation should be applied to its associated entities as well. In other words, if an entity is merged, any changes made to its state will also be propagated to its related entities that have CascadeType.MERGE specified. This ensures consistency and coherence among related entities when changes are made at the top level. (Update operations)
CascasdeType.REMOVE
As the name implies, it admits to performing a remove operation on the related entity. For instance, when a Product entity is deleted, the Product Details entity will be deleted simultaneously. There is another type which is specified and provided by Hibernate, which is CascadeType.DELETE. There is no discrepancy among them.
Orphan Removal
It is a property in relational annotations, meaning that if the parent entity has no reference, remove the child entity. Without it, what would we do?
Think about it: you have a product, and this product has images. When you want to remove the product from the database, you must remove the images connected to each other with a Foreign Key. How can you provide this in Spring Boot in a simple way? You can create an Image and Product Repository. And you can call the repositories in a service. In the service, you can remove the images that are related to this product using Product ID, and then you can remove the Product. Luckily, we have an Orpan Removal property that removes child entities if references are no longer available.
Should we use Set and List in our relations?
It depends on the requirements. Let’s look at the table and precisely decide what you need. There is no best practice. It entirely depends on what you need.
If you really need to be ordered values, use List. If you really need to have non-duplicated values, use Set.
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100)
private String name;
@Column(name = "quantity")
private Integer quantity;
@OneToOne
@JoinColumn(name = "product_detail_id")
private ProductDetail productDetail;
@ManyToOne
@JoinColumn(name = "color_id")
private Color color;
@ManyToMany
@JoinTable(
name = "product_tag",
joinColumns = @JoinColumn(name = "product_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")
)
private Set<Tag> tags;
}