Lejdi Prifti

0 %
Lejdi Prifti
Software Engineer
DevOps Engineer
ML Practitioner
  • Residence:
    Albania
  • City:
    Tirana
  • Email:
    info@lejdiprifti.com
Spring
AWS & Cloud
Angular
Team Player
Coordination & Leadership
Time Management
Docker & Kubernetes
ReactJs
JavaScript
Python
  • Java, JavaScript, Python
  • AWS, Kubernetes, Azure
  • Bootstrap, Materialize
  • Css, Sass, Less
  • Blockchain, Ethereum, Solidity
  • React, React Native, Flutter
  • GIT knowledge
  • Machine Learning, Deep Learning
0

No products in the basket.

Optimizing entities with JPA lifecycle events

19. August 2024

Introduction

In this article, we will explore how to leverage JPA entity lifecycle events, with a particular focus on @PrePersist and @PreUpdate, to enhance code readability and maintainability. By the end of this article, you will gain a clear understanding of how to utilize these lifecycle events to optimize your entities and improve overall performance.

Table of Contents

Requirements

Imagine we’re working on a project with multiple entities. In this scenario, we have two entities that are BusinessEntity, a table that stores the registered businesses and the ServiceEntity, a table that stores the services these businesses offer.  Their implementation would look as follows.

Implementation of BusinessEntity

				
					@Getter
@Setter
@Entity
@Table(name = "business_tab")
@SQLRestriction("deleted_at is null")
@NoArgsConstructor
public class BusinessEntity {

	private static final long serialVersionUID = 1L;

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

	@Column(name = "created_at", updatable = false)
	private LocalDateTime createdAt;

	@Column(name = "updated_at", insertable = false)
	private LocalDateTime updatedAt;

	@Column(name = "deleted_at", nullable = true)
	private LocalDateTime deletedAt;
	
	@Column(nullable = false)
	private String name;

	@Column(nullable = true)
	private String description;

	@Column(name = "owner_id", nullable = false)
	private String ownerId;

	@Column(name = "path", nullable = false, unique = true)
	private String path;

}

				
			

Implementation of ServiceEntity

				
					@Getter
@Setter
@Entity
@Table(name = "service_tab")
@SQLRestriction("deleted_at is null")
@NoArgsConstructor
public class ServiceEntity {
	private static final long serialVersionUID = 1L;

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

	@Column(nullable = false)
	private String name;

	@Column(nullable = false)
	private int time;

	@Column(name = "created_at", updatable = false)
	private LocalDateTime createdAt;

	@Column(name = "updated_at", insertable = false)
	private LocalDateTime updatedAt;

	@Column(name = "deleted_at", nullable = true)
	private LocalDateTime deletedAt;

}

				
			

As you can notice, entities typically share common information, such as the id, the creation date, the last updated date, and, if using soft deletion, the deletion date. 

We need to refine our code to eliminate duplicate lines. We’ll achieve this by leveraging lifecycle entities, applying the object-oriented principle of inheritance, and utilizing JPA’s @MappedSuperclass annotation.

Setting up BaseEntity

First, we need to create a base class called BaseEntity, which will contain the common information shared among our other entities.

Our other entities, BusinessEntity and ServiceEntity in this case, will extend the base class, inheriting its properties.

				
					@Data
public class BaseEntity implements Serializable {

	private static final long serialVersionUID = 4871858767721157958L;

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

	@Column(name = "created_at", updatable = false)
	private LocalDateTime createdAt;

	@Column(name = "updated_at", insertable = false)
	private LocalDateTime updatedAt;

	@Column(name = "deleted_at", nullable = true)
	private LocalDateTime deletedAt;
				
			

But our work with the BaseEntity is not over yet since we must use the @MappedSuperclass annotation of JPA.

The @MappedSuperclass annotation in Java is used to define a base class whose properties should be mapped to database columns in subclasses but doesn’t represent a database table itself.

When a class is annotated with @MappedSuperclass, its fields are inherited by subclasses, and these fields are mapped to the corresponding columns in the database tables of the subclasses. You cannot perform queries directly on a @MappedSuperclass. It’s intended only to be used as a common base class to avoid code duplication.

By using @MappedSuperclass our BaseEntity class would look as follows. 

				
					@Data
@MappedSuperclass
public class BaseEntity implements Serializable {

	private static final long serialVersionUID = 4871858767721157958L;

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

	@Column(name = "created_at", updatable = false)
	private LocalDateTime createdAt;

	@Column(name = "updated_at", insertable = false)
	private LocalDateTime updatedAt;

	@Column(name = "deleted_at", nullable = true)
	private LocalDateTime deletedAt;

}
				
			

Great! Now, our JPA entities look way cleaner. 

				
					@Getter
@Setter
@Entity
@Table(name = "business_tab")
@SQLRestriction("deleted_at is null")
@NoArgsConstructor
public class BusinessEntity extends BaseEntity {

	private static final long serialVersionUID = 1L;

	@Column(nullable = false)
	private String name;

	@Column(nullable = true)
	private String description;

	@Column(name = "owner_id", nullable = false)
	private String ownerId;

	@Column(name = "path", nullable = false, unique = true)
	private String path;

}

				
			

Setting up JPA lifecycle events

Normally, we want to store the creation date every time a new entity is created but instead of adding it manually for each of our entities, we are going to leverage the JPA lifecycle events, specifically the @PrePersist

But, what are the JPA lifecycle events?

JPA lifecycle events are callback methods that allow us to hook into the lifecycle of an entity. These events occur at specific points in an entity’s life, such as when it is persisted, updated, removed, or loaded from the database. We can use annotations to mark methods in your entity classes that should be called when these events occur.

@PrePersist is triggered before an entity is inserted into the database (before the persist operation). It is very useful for initializing default values or setting timestamps. On the other hand, @PostPersist is triggered after an entity is inserted into the database.

Moreover, @PreUpdate is triggered before an entity is updated in the database (before the merge operation). Similarly, @PostUpdate is triggered after the entity is updated in the database. 

@PreRemove is triggered before an entity is removed from the database. It is often used for cleanup tasks or for setting a “soft delete” flag. On the contrary, @PostRemove is triggered after the entity is removed from the database. 

Finally, there is also @PostLoad, which is triggered after an entity has been loaded from the database.

How can we leverage JPA lifecycle events?

We can utilize the @PrePersist annotation to automatically set the creation date of an entity. The @PreUpdate annotation will help us maintain the most recent update timestamp. 

Finally, the BaseEntity looks as follows and our other entities are clean and free of duplication.

				
					@Data
@MappedSuperclass
public class BaseEntity implements Serializable {

	private static final long serialVersionUID = 4871858767721157958L;

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

	@Column(name = "created_at", updatable = false)
	private LocalDateTime createdAt;

	@Column(name = "updated_at", insertable = false)
	private LocalDateTime updatedAt;

	@Column(name = "deleted_at", nullable = true)
	private LocalDateTime deletedAt;

	@PrePersist
	protected void setCreatedAt() {
		this.createdAt = LocalDateTime.now();
	}

	@PreUpdate
	protected void setUpdatedAt() {
		this.updatedAt = LocalDateTime.now();
	}
	
}

				
			

Whenever a new entity is inserted or an existing one is updated, the corresponding dates will be automatically refreshed.

Conclusion

Using JPA lifecycle events such as @PrePersist and @PreUpdate improves our entity management by automating the handling of important timestamps and soft delete flags. We ensure that our entities always reflect accurate creation, update, and deletion information. 

If you are interested in learning more about Spring Boot, check the Spring Boot section now.

Posted in SpringBootTags:
Write a comment