Spring Boot JPA Auditing Example (2023) | CodeUsingJava






















Spring Boot JPA Auditing Example


Auditing basically involves tracking and logging every change we make to our persisted data, which consists of tracking and storing every insert, update, and delete activity.
Auditing aids in the preservation of history records, which can later be used to trace user activity. Auditing, when done correctly, can offer us with similar functionality to version control systems.
I've seen projects where these items are manually stored. Doing so becomes extremely difficult and requires complex business logic.
But JPA and Hibernate both include automatic auditing, which makes our task easier.
in this example, we can see how we can set up JPA to capture last updated and created timestamps.This feature is implemented using the following annotations inside the Auditable class:-
CreatedBy- tells about user responsible for creation of entity.
CreatedDate- tells about the timestamps when the entity was last created.
LastModifiedBy- tells about user responsible for updation of entity,
LastModifiedDate - tells about the timestamps when the entity was last updated.
  • Project structure

    This will be the standard directory layout for maven project structure-
    Spring Boot Maven Project
    We need to start by creating a Maven pom.xml(Project Object Model) file. The pom.xml file contains the project configuration details.
    Javafaker library is used for generating real-looking fake data.
    	<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    		<modelVersion>4.0.0</modelVersion>
    		<parent>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-parent</artifactId>
    			<version>2.4.2</version>
    			<relativePath /> <!-- lookup parent from repository -->
    		</parent>
    		<groupId>com.codeusingjava</groupId>
    		<artifactId>SpringJpaAudit</artifactId>
    		<version>0.0.1-SNAPSHOT</version>
    		<name>SpringJpaAudit</name>
    		<description>JPA auditing in spring boot</description>
    		
    		<properties>
    			<java.version>1.8</java.version>
    		</properties>
    		
    		<dependencies>
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-data-jpa</artifactId>
    			</dependency>
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-web</artifactId>
    			</dependency>
    	
    			<dependency>
    				<groupId>com.h2database</groupId>
    				<artifactId>h2</artifactId>
    				<scope>runtime</scope>
    			</dependency>
    			<dependency>
    				<groupId>org.projectlombok</groupId>
    				<artifactId>lombok</artifactId>
    				<version>1.18.20</version>
    				<optional>true</optional>
    			</dependency>
    	
    			<dependency>
    				<groupId>com.github.javafaker</groupId>
    				<artifactId>javafaker</artifactId>
    				<version>1.0.2</version>
    			</dependency>
    	
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-test</artifactId>
    				<scope>test</scope>
    			</dependency>
    		</dependencies>
    	
    		<build>
    			<plugins>
    				<plugin>
    					<groupId>org.springframework.boot</groupId>
    					<artifactId>spring-boot-maven-plugin</artifactId>
    					<configuration>
    						<excludes>
    							<exclude>
    								<groupId>org.projectlombok</groupId>
    								<artifactId>lombok</artifactId>
    							</exclude>
    						</excludes>
    					</configuration>
    				</plugin>
    			</plugins>
    		</build>
    	
    	</project>
    	
    

    The application.yml file can be described as follows-
    This file contains all the configurations related to h2 database.
    	server:
      port: 8888
    spring:
      application:
        name: JPA-Auditing
      datasource:
        driverClassName: org.h2.Driver
        password: ''
        url: jdbc:h2:mem:testdb
        username: sa
      h2:
        console:
          enabled: true
          path: /h2-console
      jpa:
        database-platform: org.hibernate.dialect.H2Dialect
        hibernate:
          ddl-auto: create-drop
        properties:
          hibernate:
            show_sql: true
    
    

    The model class person can be defined as follows-
    	package com.codeusingjava.model;
    
    	import lombok.*;
    	import org.springframework.stereotype.Component;
    	
    	import com.codeusingjava.audit.Auditable;
    	
    	import javax.persistence.*;
    	@Data
    	@EqualsAndHashCode(callSuper = false)
    	@NoArgsConstructor
    	@AllArgsConstructor
    	@Builder
    	@Entity
    	@Table(name = "itemss")
    	@Component
    	public class Items extends Auditable<String> {
    	
    		@Id
    		@GeneratedValue(strategy = GenerationType.AUTO)
    		int id;
    		@Column(name = "name", nullable = false)
    		String itemsName;
    	   String material;
    		String price;
    		@Column(name = "promotionCode")
    		String promotionCode;
    	
    	}
    	
    
    The DTO class for the items can be defined as follows-
    	package com.codeusingjava.dto;
    
    	import lombok.AllArgsConstructor;
    	import lombok.EqualsAndHashCode;
    	import lombok.Getter;
    	import lombok.NonNull;
    	
    	@Getter
    	@AllArgsConstructor
    	@EqualsAndHashCode
    	public class ItemsDto {
    	
    		@NonNull
    		String itemsName;
    		String material;
    		String price;
    		String promotionCode;
    	}
    	
    

    The auditable class can be defined as follows-
    This class is used for the purpose of auditing.This helps in maintaining the logs of the data that is in the database.
    If we extend any of our classes with the Auditable class, the features of auditing will be provided to that class.
    @EntityListeners , with the help of some annotated methods, will listen for the events of the entities.
    	package com.codeusingjava.audit;
    
    	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 org.springframework.data.jpa.domain.support.AuditingEntityListener;
    	
    	import javax.persistence.*;
    	import java.util.Date;
    	
    	@Getter
    	@Setter
    	@MappedSuperclass
    	@EntityListeners(AuditingEntityListener.class)
    	public class Auditable<U> {
    	
    		@CreatedBy
    		@Column(name = "created_by", updatable = false)
    		private U createdBy;
    	
    		@CreatedDate
    		@Column(name = "created_date", updatable = false)
    		@Temporal(TemporalType.TIMESTAMP)
    		private Date creationDate;
    	
    		@LastModifiedBy
    		@Column(name = "last_modified_by")
    		private U lastModifiedBy;
    	
    		@LastModifiedDate
    		@Column(name = "last_modified_date")
    		@Temporal(TemporalType.TIMESTAMP)
    		private Date lastModifiedDate;
    	}
    	
    
    The configuration class for the bean config can be defined as follows-
    It is necessary to annotate this class with @EnableJpaAuditing annotation.
    This class will also have the object of faker for the purpose of autowiring.
    	package com.codeusingjava.config;
    
    	import com.github.javafaker.Faker;
    	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.Locale;
    	import java.util.Optional;
    	
    	@Configuration
    	@EnableJpaAuditing(auditorAwareRef = "aware")
    	public class BeanConfig {
    	
    		
    		@Bean
    		public AuditorAware<String>aware() {
    			return () -> Optional.of("Administrator");
    		}
    	
    		@Bean
    		public Faker faker() {
    			return new Faker(Locale.ENGLISH);
    		}
    	}
    	
    

    This class is used for the creation of response in the service.
    	package com.codeusingjava.model;
    
    	import lombok.EqualsAndHashCode;
    	import lombok.Getter;
    
    	@Getter
    	@EqualsAndHashCode(callSuper = true)
    	public class ItemsResponse extends ItemsDto {
    	
    		String creationDate;
    		String lastModifiedBy;
    		String lastModifiedDate;
    	
    		private ItemsResponse(
    				final String itemsName,
    				final String material,
    				final String price,
    				final String promotionCode,
    				final String creationDate,
    				final String lastModifiedBy,
    				final String lastModifiedDate) {
    			super(itemsName, material, price, promotionCode);
    			this.creationDate = creationDate;
    			this.lastModifiedBy = lastModifiedBy;
    			this.lastModifiedDate = lastModifiedDate;
    		}
    	
    		public static ItemsResponse from(final Items items) {
    			return new ItemsResponse(items.getItemsName(),
    					items.getItemsName(),
    					items.getPrice(),
    					items.getPromotionCode(),
    					items.getCreationDate().toString(),
    					items.getLastModifiedBy(),
    					items.getLastModifiedDate().toString());
    		}
    	}
    	
    
    It is necessary to extend the ItemRepository class with the JpaRepository.
    	package com.codeusingjava.repo;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    import com.codeusingjava.model.Items;
    
    @Repository
    public interface ItemsRepository extends JpaRepository<Items, Integer> {
    }
    
    
    The EntityNotFoundException can be defined as follows-
    This class comes into existence in case the entity is not found.
    	package com.codeusingjava.exception;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public class EntityNotFoundException extends Exception {
    
        private static final long serialVersionUID = 1L;
    
        public EntityNotFoundException(final String message) {
            super(message);
        }
    }
    

    The Service class can be defined as follows-
    This class defines all the necessary methods like insertion, deletion and updation of data. In case, the entity with the particular id is not found, then exception will be thrown.
    	package com.codeusingjava.service;
    
    import com.codeusingjava.dto.ItemsDto;
    import com.codeusingjava.dto.ItemsResponse;
    import com.codeusingjava.exception.EntityNotFoundException;
    import com.codeusingjava.model.Items;
    import com.codeusingjava.repo.ItemsRepository;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Slf4j
    @Service
    public class ItemsService {
    
        @Autowired
        ItemsRepository repository;
    
        public void saveAll(final List<Items> itemss) {
            repository.saveAll(itemss);
        }
    
        public List<ItemsResponse> getAll() {
            log.info("Getting all itemss");
            return repository.findAll()
                    .stream()
                    .map(ItemsResponse::from)
                    .collect(Collectors.toList());
        }
    
        public Items getItems(final int id) throws EntityNotFoundException {
            log.info("Getting items id = {}", id);
            return repository.findById(id).orElseThrow(() ->
                    new EntityNotFoundException(String.format("Items %s not found", id)));
        }
    
        public void updateItems(final int id, final ItemsDto dto) throws EntityNotFoundException {
            log.info("Updating items id = {}", id);
            getItems(id);
            final Items p = Items.builder()
            		.id(id)
                    .material(dto.getMaterial())
                    .price(dto.getPrice())
                    .promotionCode(dto.getPromotionCode())
                    .build();
            repository.save(p);
        }
    }
    
    
    The controller class can be defined as follows-
    This class contains all the necessary endpoints of the program.
    	package com.codeusingjava.controller;
    
    import com.codeusingjava.dto.ItemsDto;
    import com.codeusingjava.dto.ItemsResponse;
    import com.codeusingjava.exception.EntityNotFoundException;
    import com.codeusingjava.model.Items;
    import com.codeusingjava.service.ItemsService;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    
    @RestController
    @RequestMapping("/api")
    public class ItemsController {
    
        @Autowired
        ItemsService service;
    
        @GetMapping("/itemss")
        @ResponseStatus(code = HttpStatus.OK)
        public List<ItemsResponse> getItemss() {
            return service.getAll();
        }
    
        @GetMapping("/items-by-id")
        @ResponseStatus(code = HttpStatus.OK)
        public ItemsResponse getItems(@RequestParam(name = "id") final int id)
                throws EntityNotFoundException {
            final Items p = service.getItems(id);
            return ItemsResponse.from(p);
        }
    
        @PutMapping("/items-by-id")
        @ResponseStatus(code = HttpStatus.NO_CONTENT)
        public void updateItems(@RequestParam(name = "id") final int id,
                                  @RequestBody final ItemsDto dto) throws EntityNotFoundException {
            service.updateItems(id, dto);
        }
    }
    
    
    The main class for the Spring Boot JPA-Auditing application can be defined as follows-
    The annotation @Slf4j is used for the generation of logs with default name log using the API of Slf4j.
    	package com.codeusingjava;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @SpringBootApplication
    public class SpringDataJpaAuditingApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SpringDataJpaAuditingApplication.class, args);
    		log.info("Spring boot and jpa auditing application");
    	}
    }
    
    
If we now run the application we get the output as follows-
We can see that all the tables are automatically generated while creating the database.This is done with the help of JPA and Hibernate implementation.
output
If we run the following url in the REST CLIENT, we get the following output-
We can see that with the help of Spring Data JPA auditing, the logs can be automatically updated.
output1

output2

Download the code-

Spring Boot JPA Auditing Example