Spring Boot Transaction Management + JPA Example | CodeUsingJava








Spring Boot Transaction Management + JPA Example


Introduction:

Transaction is a set of logical operations that needs to be performed in a definite and ordered manner. Transaction Management is an effective methodology that help's in achieving efficient and unambiguous transactions. Transaction Management aims to offer consistency and validity of the data by facilitating ACID characteristics.

What is ACID ?

ACID stands for Atomicity, Consistency, Isolation, and Durability that help's in exception handling and maintaining proper track of transaction processed.

  • Atomicity - It ensures that either all or none of the operations needs to be performed.
  • Consistency - It ensures that either all operations are rolled back and set to the state back from where the transaction was started or the changes were successfully made and reflected.
  • Isolation - It ensures that transactions performed are only reflected after a commit.
  • Durability - It ensures that the committed transactions are persisted.

Operations to control ACID transactions using JDBC:

  • Deactivate auto-commit, this provides you the control to execute multiple SQL statements within a single transaction; else you will end up executing each SQL statement as a separate transaction.
  • Every transaction needs to be committed by calling commit() method, this allows to persists those changes within the database permanently, else the changes won't be reflected within the database.
  • In case if an error or an exception is detected within the business logic or SQL failed to execute, rollback all the operations by calling the rollback() method, else there'll be a inconsistency in data persisted within the database.
  • Monolithic Spring Boot Application -

    Monolithic Application

    This will be the standard directory layout for maven project structure-

    Spring Boot Hazelcast 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.

    pom.xml

    <?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.3.4.RELEASE</version>
    		<relativePath /> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.code.javainuse</groupId>
    	<artifactId>SimpleTransactionManagement</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>SimpleTransactionManagement</name>
    	<description>Transaction Management using 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>org.springframework.boot</groupId>
    			<artifactId>spring-boot-devtools</artifactId>
    			<scope>runtime</scope>
    			<optional>true</optional>
    		</dependency>
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<scope>runtime</scope>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    			<exclusions>
    				<exclusion>
    					<groupId>org.junit.vintage</groupId>
    					<artifactId>junit-vintage-engine</artifactId>
    				</exclusion>
    			</exclusions>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    

    In pom.xml comprises the below plugins

    • Spring-Boot-Starter-Web - Provides starter for building RESTful applications. It uses Tomcat as a default embedded container.
    • Spring-Boot-Devtools - Enables automatic restart of the application whenever files in classpath are modified.
    • Spring-Boot-Data-JPA - Permits storing data using JPA into Relational database.
    • MySQL-Connector-Java - It is the Official JDBC driver for MYSQL.

    Create the Student model class with the JPA annotations for mapping java object to database table as follows-

    Student.java

    package com.code.javainuse.model;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "StudentInfo")
    public class Student {
    
    	@Id
    	int studentId;
    	String studentName;
    	int studentAge;
    
    	public Student() {
    		super();
    	}
    
    	public Student(int studentId, String studentName, int studentAge) {
    		super();
    		this.studentId = studentId;
    		this.studentName = studentName;
    		this.studentAge = studentAge;
    	}
    
    	public int getStudentId() {
    		return studentId;
    	}
    
    	public void setStudentId(int studentId) {
    		this.studentId = studentId;
    	}
    
    	public String getStudentName() {
    		return studentName;
    	}
    
    	public void setStudentName(String studentName) {
    		this.studentName = studentName;
    	}
    
    	public int getStudentAge() {
    		return studentAge;
    	}
    
    	public void setStudentAge(int studentAge) {
    		this.studentAge = studentAge;
    	}
    
    	@Override
    	public String toString() {
    		return "Student [studentId=" + studentId + ", studentName=" + studentName + ", studentAge=" + studentAge + "]";
    	}
    
    }
    
    

    Student.java is POJO class

    • @Entity annotation peforms mapping the Student class with the student_info table.
    • @Table annotation allows overriding the name of the table from student to student_info.
    • @Id annotations helps to mark an attribute as a primary key.

    Create the RestController for performing StudentRegistration operations. We will be autowiring the StudentServiceImpl and use it to perform Service operations.

    The StudentController act's as a gateway for accessing the application services and perform the operations based on type of the request.

    StudentController.java

    package com.code.javainuse.controller;
    
    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.code.javainuse.model.Student;
    import com.code.javainuse.service.StudentServcie;
    
    @RestController
    public class StudentController {
    
    	@Autowired
    	StudentServcie studentService;
    
    	@GetMapping("/retrieve")
    	public List<Student> retrieve() {
    		List<Student> studentList = studentService.retrieve();
    		return studentList;
    	}
    
    	@PostMapping("/insert")
    	public String insert(@RequestBody Student student) {
    		if (student == null) {
    			return "Failed to insert Student within the database";
    		} else {
    			String result = studentService.insert(student);
    			return result;
    		}
    	}
    
    	@DeleteMapping("/delete")
    	public String delete(int studentId) {
    		String result = studentService.delete(studentId);
    		return result;
    	}
    }
    

    Create a service StudentServiceImpl. Here StudentServiceImpl class implements StudentService interface following the MVC model.

    StudentService.java

    
    package com.code.javainuse.service;
    
    import java.util.List;
    
    import com.code.javainuse.model.Student;
    
    public interface StudentServcie {
    
    	public List<Student> retrieve();
    	public String insert(Student student);
    	public String delete(int StudentId);
    }
    
    

    StudentServiceImpl.java

    package com.code.javainuse.service;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.code.javainuse.model.Student;
    import com.code.javainuse.repository.StudentRepository;
    
    @Service
    public class StudentServiceImpl implements StudentServcie {
    
    	@Autowired
    	StudentRepository studentRepository;
    
    	@Override
    	@Transactional
    	// A Proxy is Created that wraps the function retrieve
    	// BeginTransaction
    	public List<Student> retrieve() {
    		List<Student> studentList = (List<Student>) studentRepository.findAll();
    		return studentList;
    	}
    	// Commit Transaction
    
    	@Override
    	@Transactional
    	// A Proxy is Created that wraps the function insert
    	// BeginTransaction
    	public String insert(Student student) {
    		Student save = studentRepository.save(student); // Call to the Repository
    		if (save.getStudentId() == 2) {
    			int a = 1 / 0;
    			System.out.println("The value of A is : " + a);
    		}
    		if (save != null) {
    			return "The Student is successfully inserted within the database";
    		}
    		return "Failed to insert Student within the database";
    	}
    	// Commit Transaction
    
    	@Override
    	@Transactional
    	// A Proxy is Created that wraps the function delete
    	// BeginTransaction
    	public String delete(int StudentId) {
    		studentRepository.deleteById(StudentId);
    		return "The Student is successfully deleted from the database";
    	}
    	// Commit Transaction
    
    }
    

    StudentServiceImpl.java implements Transaction Management methodology by facilitating @Transactional annotation that builds a wrapper around the StudentServiceImpl functions such as retrieve, insert, delete etc.

    The @Transactional annotation intents to automatically include the boiler plate code for disabling auto-commit, Commit(), Rollback() function. This helps in rolling back the database operation in case if any exception or error arises, ensuring consistency and effectiveness in processing data by implementing ACID property.

    Note :

    • @Transaction annotation will work for only public methods of the class Annotated by @Service annotation.
    • It is necessary that the Service class StudentServiceImple is Autowired, else @Transactional anotation will not work.

    Create the repository by extending the CrudRepository. By extending CrudRepository we get a bunch of generic CRUD methods into our type that allows saving , deleting and retrieving Student.

    StudentRepository.java

    
    package com.code.javainuse.repository;
    
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.stereotype.Repository;
    
    import com.code.javainuse.model.Student;
    
    @Repository
    public interface StudentRepository extends CrudRepository<Student, Integer> {
    
    }
    
    

    Next we create the Spring Boot class with the SpringBootApplication annotation.

    SimpleTransactionManagement
    Application.java

    
    package com.code.javainuse;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SimpleTransactionManagementApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SimpleTransactionManagementApplication.class, args);
    	}
    
    }
    

    Finally create the application.properties file with database properties as follows-

    application.properties

    
    ## MySQL
    spring.datasource.url=jdbc:mysql://localhost:3306/student
    spring.datasource.username=root
    spring.datasource.password=root
    
    # drop n create table, good for testing, comment this in production
    spring.jpa.hibernate.ddl-auto=update
    
    # Allows Hibernate to generate SQL optimized for a particular DBMS
    spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
    
    # Show or not log for each sql query
    spring.jpa.show-sql = true
    
    

    Start the Spring Boot Application and verify if the table is created and maps with the attributes of Student.java POJO class.



    MysqlEmptyTable

    MysqlDescTable

    Let's send a request to http://localhost:8080/insert url and insert a Student within the database.

    PostmanInsertFinalMark

    MysqlInsert

    Now lets send another request to verify if the transaction is rolled backed on occurance of an exception, As we have mentioned that if the StudentId is 2 then throw a divide by zero exception.

    InsertCode PostmanInsertFinal2

    Now, lets check the logs to get a clear understanding.

    LogConsole1

    Hence, in the above log we can clearly see that the transaction was initiated for first request and commited successfully.

    LogConsole2

    However, for the second request the Transaction was initiated but was rolled back due to the Divide By Zero Exception.

    Now let's verify if the the student is inserted within the database or no.

    Spring Boot Hazelcast Rest Client Project

    We can clearly see, that there is only one record present within the database and the second student record named "kshah" is not inserted within the database.

    Thank you, This was a basic example on Transaction Management using Spring Boot and JPA.

Downloads-

Spring Boot + Data JPA + Transaction Management Example