Before we do that, we need to upgrade the versions for our dependencies since I have been using this project for quite sometime. Also I decided to use lombok logging annotation and removed all the dependencies on log4j.
Here are the modifications to the pom.xml for setting dependencies.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>in.springframework.blog</groupId> | |
<artifactId>tutorials</artifactId> | |
<version>0.0.1-SNAPSHOT</version> | |
<packaging>jar</packaging> | |
<name>tutorials</name> | |
<description>Demo project for Spring Boot</description> | |
<parent> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-parent</artifactId> | |
<version>2.3.0.RELEASE</version> | |
<relativePath/> <!-- lookup parent from repository --> | |
</parent> | |
<properties> | |
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | |
<java.version>1.8</java.version> | |
<swagger.version>2.9.2</swagger.version> | |
<gson.version>2.8.6</gson.version> | |
<log4j.version>2.13.3</log4j.version> | |
</properties> | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
<exclusions> | |
<exclusion> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-tomcat</artifactId> | |
</exclusion> | |
</exclusions> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.kafka</groupId> | |
<artifactId>spring-kafka</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.apache.kafka</groupId> | |
<artifactId>kafka-clients</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>mysql</groupId> | |
<artifactId>mysql-connector-java</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-data-jpa</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-jetty</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.eclipse.jetty</groupId> | |
<artifactId>jetty-deploy</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.eclipse.jetty</groupId> | |
<artifactId>jetty-rewrite</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.eclipse.jetty</groupId> | |
<artifactId>jetty-util</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-security</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-expression</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-configuration-processor</artifactId> | |
<optional>true</optional> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.security</groupId> | |
<artifactId>spring-security-web</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.security</groupId> | |
<artifactId>spring-security-config</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-actuator</artifactId> | |
</dependency> | |
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> | |
<dependency> | |
<groupId>org.apache.logging.log4j</groupId> | |
<artifactId>log4j-core</artifactId> | |
<version>${log4j.version}</version> | |
</dependency> | |
<dependency> | |
<groupId>com.google.code.gson</groupId> | |
<artifactId>gson</artifactId> | |
<version>${gson.version}</version> | |
</dependency> | |
<dependency> | |
<groupId>io.springfox</groupId> | |
<artifactId>springfox-swagger2</artifactId> | |
<version>${swagger.version}</version> | |
</dependency> | |
<dependency> | |
<groupId>io.springfox</groupId> | |
<artifactId>springfox-swagger-ui</artifactId> | |
<version>${swagger.version}</version> | |
</dependency> | |
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> | |
<dependency> | |
<groupId>org.projectlombok</groupId> | |
<artifactId>lombok</artifactId> | |
<scope>provided</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-test</artifactId> | |
<scope>test</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.security</groupId> | |
<artifactId>spring-security-test</artifactId> | |
<scope>test</scope> | |
</dependency> | |
</dependencies> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-maven-plugin</artifactId> | |
</plugin> | |
<plugin> | |
<groupId>pl.project13.maven</groupId> | |
<artifactId>git-commit-id-plugin</artifactId> | |
<version>2.2.1</version> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package in.springframework.blog.tutorials.user.domain; | |
import lombok.Getter; | |
import lombok.Setter; | |
import org.springframework.data.annotation.CreatedBy; | |
import org.springframework.data.annotation.CreatedDate; | |
import org.springframework.data.annotation.LastModifiedBy; | |
import org.springframework.data.annotation.LastModifiedDate; | |
import javax.persistence.*; | |
import java.util.Date; | |
@Getter | |
@Setter | |
@MappedSuperclass | |
public abstract class AbstractBaseEntity { | |
@Id | |
@GeneratedValue(strategy= GenerationType.AUTO) | |
private Long id; | |
@CreatedBy | |
private Long createdBy; | |
@LastModifiedBy | |
private Long updatedBy; | |
@CreatedDate | |
private Date createdAt; | |
@LastModifiedDate | |
private Date updatedAt; | |
} |
As we can see, we have added five attributes in the base class. One is the id for all our entities and rest four will be used for auditing purposes.
At this time we also add another layer to our code. Currently all the Endpoints directly call the Repository layer, this causes a problem if we want to write functions that can be reused across different endpoints. An example of this need is retrieveUser method that takes an argument that could be a username or a email. Currently this method lies in the Endpoint layer as a private method. This is a useful method in many different contexts, so we create a new UserService layer and move this method there.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package in.springframework.blog.tutorials; | |
import in.springframework.blog.tutorials.user.domain.User; | |
import in.springframework.blog.tutorials.user.repository.UserRepository; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import java.util.Optional; | |
@Service | |
public class UserService { | |
@Autowired | |
private UserRepository userRepository; | |
public Iterable<User> findAll() { | |
return userRepository.findAll(); | |
} | |
public Optional<User> retrieveUser(String idOrUserNameOrEmail) { | |
try { | |
Long id = Long.parseLong(idOrUserNameOrEmail); | |
Optional<User> optionalUser = userRepository.findById(id); | |
if (optionalUser.isPresent()) { | |
return optionalUser; | |
} | |
} | |
catch(NumberFormatException e) { | |
} | |
Optional<User> optionalUser = userRepository.findUserByEmail(idOrUserNameOrEmail); | |
if (optionalUser.isPresent()) { | |
return optionalUser; | |
} | |
optionalUser = userRepository.findUserByUsername(idOrUserNameOrEmail); | |
return optionalUser; | |
} | |
public User save(User user) { | |
return userRepository.save(user); | |
} | |
public void delete(User user) { | |
userRepository.delete(user); | |
} | |
} |
Now, let's get to the original task of enabling auditing. First we define a Auditing Config as below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package in.springframework.blog.tutorials.configs; | |
import in.springframework.blog.tutorials.RequestContext; | |
import in.springframework.blog.tutorials.user.domain.User; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.data.domain.AuditorAware; | |
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; | |
import java.util.Optional; | |
@Configuration | |
@EnableJpaAuditing(auditorAwareRef = "auditorAware") | |
public class EntityAuditConfig { | |
@Bean | |
public AuditorAware<Long> auditorAware() { | |
return new AuditorAware<Long>() { | |
@Override | |
public Optional<Long> getCurrentAuditor() { | |
return Optional.of(RequestContext.currentUser.get().getId()); | |
} | |
}; | |
} | |
} |
We had earlier defined a ThreadLocal that is used by the auditAware method defined above to extract currently logged in user and return its userId. As we can see the audit fields in the AbstractBaseEntity expects a Long for @createdBy and @LastModifiedBy fields. The EntityAuditConfig also has annotation @EnableJpaAuditing which is required.
At this point we also add a new endpoint called ProfileEndpoint which can be used to manage the entity that represents a user profile. This entity currently only contains a url.
Now if we perform any operation on any of the endpoint, we will see the auditing fields automatically populated. Give it a spin. It is a life saver in many productions applications. I have had situations where users changed their passwords, forgot them and then complained saying that they have been hacked.
The complete code for this and previous posts can be found at my github repository. This tutorial changes are under v1.5.