Skip to content

POC to demonstrate automation of entity fields such as created_at, created_by, updated_by using JPA @Embedded, @embeddable and ThreadLocal

Notifications You must be signed in to change notification settings

hardikSinghBehl/entity-activity-automator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Automating Entity Activity-fields in a Java Spring-boot project

Activity-fields in this context are being used to refer to fields such as created_at, created_by, updated_at and updated_by that are common in any entity for which CRUD APis are exposed

Approach Implemented using

  • JPA's
    • @Embedded
    • @Embeddable
    • @PrePersist
    • @PreUpdate
  • ThreadLocal.class
  • Http Request Filters (OncePerRequestFilter.class)

can ⭐️ Repository if found helpful or leave comments in Discussions 💬😄

Sample Screen-recording added below (120-seconds) 🤝


Important classes and files


Explanation

  • Problem Statement

For every entity for which CRUD operation is performed, there are some columns that are repeated for all of them (created_at, created_by etc are used in this POC). In every POST and PUT operation the developer has to expicility provide values in the columns.

  • Goal

EXAMPLE : If a table contains 5 columns, full_name and the 4 activity columns (mentioned above), the code in the service layer would look like the one below:

var user = new User();
user.setFullName("Whatever came from frontend");
user.setCreatedAt(current-time);
user.setCreatedBy(id-from-decoding-JWT);
user.setUpdatedAt(current-time);
user.setUpdatedBy(id-from-decoding-JWT);

db.save(user);

The goal of this POC is to convert the above code to the one below, with the values in the remaining column being filled automatically:

var user = new User();
user.setFullName("Whatever came from frontend");
db.save(user);

while the migration scipt remains the same 😄

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT UUID(),
  full_name VARCHAR(100) NOT NULL,
  created_at TIMESTAMP NOT NULL,
  created_by UUID NOT NULL, 
  updated_at TIMESTAMP NOT NULL,
  updated_by UUID NOT NULL
);
  • Solution

  1. Create a class Activity and annotate it with @Embeddable, define the columns that are to be kept common in the required entities. Using @PrePersist and @PreUpdate we'll take care of putting the values in the 4 fields.
@Embeddable
@Data
public class Activity {

    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;
    @Column(name = "created_by", nullable = false, updatable = false)
    private UUID createdBy;
    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;
    @Column(name = "updated_by", nullable = false)
    private UUID updatedBy;
}
  1. Using @Embedded annotation, define an instance of the above created class in the Entity classes
@Entity
@Table(name = "patients")
@Data
public class Patient implements Serializable {

    private static final long serialVersionUID = 7906541761495255102L;

    @Id
    private UUID id;

    @Column(name = "full_name", nullable = false, length = 100)
    private String fullName;

    @Embedded
    @Setter(AccessLevel.NONE)
    private Activity activity = new Activity();

}
  1. Taking care of created_at and updated_at (timesatmp fields) using JPA's @PrePersist and @PreUpdate inside the Activity class created in the first step
@PrePersist
void onCreate() {
    this.createdAt = LocalDateTime.now(ZoneId.of("+00:00"));
    this.updatedAt = LocalDateTime.now(ZoneId.of("+00:00"));
}

@PreUpdate
void onUpdate() {
    this.updatedAt = LocalDateTime.now(ZoneId.of("+00:00"));
}
  1. Defining a class with an instance of ThreadLocal.class to store primary-id of logged-in user with Getters and Setters
public class LoggedInDoctorDetailProvider {

    private static final ThreadLocal<UUID> userId = new ThreadLocal<UUID>();

    public static void setUserId(final UUID id) {
        userId.set(id);
    }

    public static UUID getId() {
        return userId.get();
    }

}
  1. Creating a filter class extending OncePerRequestFilter.class, to intercept the details of logged-in user who's already authenticated in the JwtAuthenticationFilter.java
Make sure the created filter executes after the Authentication filter, in this POC it's added in the filter-chain using Spring-security with addAfter()

The primary-id (UUID in this case) is to be stored in the above created class in the ThreadLocal instance, so that only that particular thread can access it (Thread-per-request model is used in a REST-API project) using the defined setter method

if (authentication != null) {
    final String authorizationHeader = request.getHeader("Authorization");
    final String token = authorizationHeader.substring(7);
    final UUID userId = jwtUtils.extractDoctorId(token);

    LoggedInDoctorDetailProvider.setUserId(userId);
}
  1. Taking care of created_by and updated_by using JPA's @PrePersist and @PreUpdate inside the Activity class created in the first step, ThreadLocal instance will provide the primary-id of the authenticated/logged-in user
@PrePersist
void onCreate() {
    this.createdBy = LoggedInDoctorDetailProvider.getId();
    this.updatedBy = LoggedInDoctorDetailProvider.getId();
}

@PreUpdate
void onUpdate() {
    this.updatedBy = LoggedInDoctorDetailProvider.getId();
}

Recording Demonstration

entity-activity-fields-automation-test-recording.mov

Local Setup Guide

  • Install Java 17 (recommended to use SdkMan)
sdk install java 17-open
  • Install Maven (recommended to use SdkMan)
sdk install maven
  • Clone the repo and run the below command in the core
git clone https://github.com/hardikSinghBehl/entity-activity-automator.git
mvn clean install
  • To start the application, run any of the below 2 commands in the core
mvn spring-boot:run &
java -jar target/entity-activity-embeder-0.0.1-SNAPSHOT.jar &
  • Access the swagger-ui
http://localhost:8080/swagger-ui.html

References

About

POC to demonstrate automation of entity fields such as created_at, created_by, updated_by using JPA @Embedded, @embeddable and ThreadLocal

Topics

Resources

Stars

Watchers

Forks

Languages