Spring Boot Transaction Management Tutorial with Code Example (2020) | TechGeekNext >>








Spring Boot Transaction Management Example


In this tutorial, we will understand transaction management by using the @transactional annotation by implementing the spring boot + JPA example.

What is database transaction?

A database transaction is a sequence of actions that are treated as a single unit of work. Transaction management is an essential aspect of RDBMS application to ensure data integrity and consistency. The transaction can be defined with ACID properties.

  1. Atomicity - All success or none.
  2. Consistency - Integrity constraints must be maintained in such a way that the database is consistent before and after the transaction.
  3. Isolation - One transaction should not impact on another transaction.
  4. Durability - After completion of the transaction, the changes and modifications to the database will be stored in and written to the disk and will continue even if a system failure occurs.
There are 2 ways to achieve transaction management in Spring:
  1. Spring Programmatic Transaction Management

    With programmatic transactions, transaction management code needs to be explicitly written so as to commit when everything is successful and rolling back if anything goes wrong. The transaction management code is tightly bound to the business logic in this case.
    UserNoteTransaction userNoteTransaction = entityManager.getTransaction();
    try {
       //Begin Transaction
        userNoteTransaction.begin();
    
        /* register user - query 1
    	 create note
    	link note to user - query 2 */
    
        //Commit Transaction
        userNoteTransaction.commit();
    } catch(Exception exception) {
        //Rollback Transaction
        userNoteTransaction.rollback();
        throw exception;
    } 
  2. Spring Declarative Transaction Management

    Using @Transactional, the above code gets reduced to simply this:
    @Transactional
    public void addNoteToSpecificUser() {
        /* register user - query 1
    	 create note
    	 link note to user - query 2 */
    }

In Trasaction Management, different types of Trasaction Propagation specifies whether or not the corresponding component will participate in the transaction and what happens if the calling component/service has or does not already have a transaction created/started.

Now let's begin to create Spring Boot application with JPA to add notes to a specfic users to handle transaction management with services as given below.

  1. UserService to create new user.
  2. NotesService to add Note.
  3. UserNotesLinkService to link above created user with Note.
Take a look at our suggested posts:

Project Structure

Following is pom.xml, with added dependencies for jpa.

<?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.2.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.techgeeknext</groupId>
	<artifactId>spring-boot-transaction-management</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-transaction-management</name>
	<description>Demo project for Spring Boot Transaction Management</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</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>

Create application.properties as follows:


spring.datasource.url=jdbc:mysql://localhost/transactionExampleDB?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.platform=mysql
spring.datasource.initialization-mode=always

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = create-drop

UserService

This interface is used to provide opration to register new user.
package com.techgeeknext.service;

import com.techgeeknext.modal.NoteBase;
import com.techgeeknext.modal.User;
import org.springframework.stereotype.Service;

@Service
public interface UserService {
    public User registerUser(User user);
}
UserServiceImpl is the implantation class of UserService interface, registerUser method is used to create new user by invoking user repository method to save the data in the database.
package com.techgeeknext.service.impl;

import com.techgeeknext.modal.User;
import com.techgeeknext.repository.UserRepository;
import com.techgeeknext.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements  UserService{
    @Autowired
    UserRepository userRepository;

    @Override
    public User registerUser(User user) {
        return userRepository.save(user);
    }
}

NotesService

This interface is used to provide operation to add new Note.
package com.techgeeknext.service;
import java.util.List;
import javax.validation.Valid;
import com.techgeeknext.modal.NoteBase;
import com.techgeeknext.modal.Notes;
import com.techgeeknext.modal.User;
import org.springframework.stereotype.Service;

@Service
public interface NotesService {
	public String addNote(Notes note) throws Exception;
}
NotesServiceImpl is the implemtation class of NotesService interface, addNote method is used to create new note by invoking note repository method to save the data in the database.
package com.techgeeknext.service.impl;
import java.util.List;
import javax.validation.Valid;
import com.techgeeknext.modal.NoteBase;
import com.techgeeknext.modal.Notes;
import com.techgeeknext.modal.User;
import com.techgeeknext.repository.NotesRepository;
import com.techgeeknext.repository.UserRepository;
import com.techgeeknext.service.NotesService;
import com.techgeeknext.util.ApplicationConstants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class NotesServiceImpl implements NotesService {
	private static final Logger log = LogManager.getLogger(NotesServiceImpl.class);

	@Autowired
	private NotesRepository noteRepository;

	@Override
	public String addNote(Notes note){
		log.info("Inside add note service");
		noteRepository.save(note);
		log.info("Successfully added new Note.");
		return ApplicationConstants.ADDED_NOTE_DESC;
	}
}

UserNotesLinkService

This interface is used to provide opration to link above created User with Note.
package com.techgeeknext.service;

import com.techgeeknext.modal.Notes;
import com.techgeeknext.modal.User;
import org.springframework.stereotype.Service;
@Service
public interface UserNotesLinkService {
    public String addNoteToSpecificUser(User user, Notes note) throws Exception;
}
UserNotesLinkServiceImpl is the implemtation class of UserNotesLinkService interface, addNoteToSpecificUser method is used to link user with note. Will first save the user by calling User Service and then create note and set created new user in dbNote object and call Note Service to save note in database.

@Transactional annotation will do all the magic we need to do in the programmatic way before to handle transaction management. Any bean's public method you annotate with the @Transactional annotation, will be executed within a transaction management.

package com.techgeeknext.service.impl;

import com.techgeeknext.modal.Notes;
import com.techgeeknext.modal.User;
import com.techgeeknext.service.NotesService;
import com.techgeeknext.service.UserNotesLinkService;
import com.techgeeknext.service.UserService;
import com.techgeeknext.util.ApplicationConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserNotesLinkServiceImpl implements UserNotesLinkService {
    @Autowired
    private UserService userService;

    @Autowired
    private NotesService notesService;

    @Override
    @Transactional
    public String addNoteToSpecificUser(User user, Notes note) throws Exception {
        //create new user
        User createdUser = userService.registerUser(user);
        Notes dbNote = new Notes();
        dbNote.setTitle(note.getTitle());
        dbNote.setMessage(note.getMessage());
        //set created user to note
        dbNote.setUserDetails(createdUser);
        //persist new note
        notesService.addNote(dbNote);
        return ApplicationConstants.ADDED_NOTE_DESC;
    }
}

JPA Repository (Data Layer)

This repository allows us to access the information stored in the database by using JPA. We have used CrudRepository. CrudRepository mainly provides CRUD functions while JpaRepository provide some JPA related methods such as flushing the persistent context, deleting records in a batch.


UserRepository
This Repository class will persist user related data in database.
package com.techgeeknext.repository;
import com.techgeeknext.modal.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

NotesRepository
This Repository class will persist notes related data in database.
package com.techgeeknext.repository;

import com.techgeeknext.modal.Notes;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface NotesRepository extends CrudRepository<Notes, Long>{
}

Main Application

TransactionManagementApplication is the main class, which contains test data to test transaction management. We used @EnableJpaAuditing to use Spring JPA to automatically persist the CreatedBy, CreatedDate, LastModifiedBy, and LastModifiedDate columns for any entity.
package com.techgeeknext;

import com.techgeeknext.modal.Notes;
import com.techgeeknext.modal.User;
import com.techgeeknext.service.UserNotesLinkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableJpaAuditing
@RestController
public class TransactionManagementApplication {

	@Autowired
	private UserNotesLinkService userNotesService;

	public static void main(String[] args) throws Exception {
		ApplicationContext context = SpringApplication.run(TransactionManagementApplication.class, args);
		UserNotesLinkService userNotesLinkService = context.getBean(UserNotesLinkService.class);

		/* TEST DATA FOR TRANSACTION MANAGEMENT EXAMPLE*/
		// create new user
		User user = new User();
		user.setUserMail("techgeeknext@gmail.com");
		user.setUserPass("12345678jhg");

		//create new note
		Notes note = new Notes();
		note.setTitle("Test Note");
		note.setMessage("Test Message");

		//link above new user with above note
		userNotesLinkService.addNoteToSpecificUser(user, note);
	}
}

Transaction Management in Spring Boot is a cross cutting concern and it is implemented using AOP (same as Transaction Advice).

As shown below in diagram, Spring internally creates a proxy for the class or method annotated with @transaction annotation. A proxy allows to inject before,after and around methods calls into the object being proxied.

The generated proxy object is provided with a Transaction Interceptor, which is created by Spring. So when the @Transactional method is invoked from the client code, the Transaction Interceptor will first be invoked from the proxy object, which will start the transaction and eventually invoke the method on the target bean (i.e. your bean). When the invocation completed, the TransactionInterceptor commits/rollback the transaction properly.

Test the Application


Run the application by using IDE Run option or by using below command
mvn spring-boot:run
C:\Users\softwares\jdk1.8.0_91\bin\java.exe -Dmaven.multiModuleProjectDirectory=D:\Site\Examples\SpringBootTransactionMngmt\techgeeknext\spring-boot-transaction-management "-Dmaven.home=C:\Program Files\JetBrains\ideaIC-2020.1.win\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\ideaIC-2020.1.win\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\ideaIC-2020.1.win\plugins\maven\lib\maven-event-listener.jar" "-javaagent:C:\Program Files\JetBrains\ideaIC-2020.1.win\lib\idea_rt.jar=63958:C:\Program Files\JetBrains\ideaIC-2020.1.win\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\ideaIC-2020.1.win\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar" org.codehaus.classworlds.Launcher -Didea.version2020.1 spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] --------< com.techgeeknext:spring-boot-transaction-management >---------
[INFO] Building spring-boot-transaction-management 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.2.6.RELEASE:run (default-cli) > test-compile @ spring-boot-transaction-management >>>
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ spring-boot-transaction-management ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-boot-transaction-management ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ spring-boot-transaction-management ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\Site\Examples\SpringBootTransactionMngmt\techgeeknext\spring-boot-transaction-management\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ spring-boot-transaction-management ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.2.6.RELEASE:run (default-cli) < test-compile @ spring-boot-transaction-management <<<
[INFO]
[INFO]
[INFO] --- spring-boot-maven-plugin:2.2.6.RELEASE:run (default-cli) @ spring-boot-transaction-management ---
[INFO] Attaching agents: []

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.6.RELEASE)

2020-04-28 19:06:41.402  INFO 26284 --- [  restartedMain] c.t.TransactionManagementApplication     : Starting TransactionManagementApplication on LAPTOP-S6R44CQL with PID 26284 (D:\Site\Examples\SpringBootTransactionMngmt\techgeeknext\spring-boot-transaction-management\target\classes started in D:\Site\Examples\SpringBootTransactionMngmt\techgeeknext\spring-boot-transaction-management)
2020-04-28 19:06:41.417  INFO 26284 --- [  restartedMain] c.t.TransactionManagementApplication     : No active profile set, falling back to default profiles: default
2020-04-28 19:06:41.541  INFO 26284 --- [  restartedMain] o.s.b.devtools.restart.ChangeableUrls    : The Class-Path manifest attribute in C:\Users\\.m2\repository\org\glassfish\jaxb\jaxb-runtime\2.3.2\jaxb-runtime-2.3.2.jar referenced one or more files that do not exist: file:/C:/Users/ .m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.xml.bind-api-2.3.2.jar,file:/C:/Users//.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/txw2-2.3.2.jar,file:/C:/Users//.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/istack-commons-runtime-3.0.8.jar,file:/C:/Users//.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/stax-ex-1.8.1.jar,file:/C:/Users//.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/FastInfoset-1.2.16.jar,file:/C:/Users//.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.2/jakarta.activation-api-1.2.1.jar
2020-04-28 19:06:41.541  INFO 26284 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2020-04-28 19:06:41.541  INFO 26284 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2020-04-28 19:06:43.098  INFO 26284 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-04-28 19:06:43.199  INFO 26284 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 90ms. Found 2 JPA repository interfaces.
2020-04-28 19:06:44.780  INFO 26284 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-04-28 19:06:44.799  INFO 26284 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-04-28 19:06:44.800  INFO 26284 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-04-28 19:06:44.976  INFO 26284 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-04-28 19:06:44.976  INFO 26284 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3435 ms
2020-04-28 19:06:45.293  INFO 26284 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-04-28 19:06:45.402  INFO 26284 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.12.Final
2020-04-28 19:06:45.623  INFO 26284 --- [  restartedMain] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-04-28 19:06:45.823  INFO 26284 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-04-28 19:06:46.159  INFO 26284 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-04-28 19:06:46.195  INFO 26284 --- [  restartedMain] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2020-04-28 19:06:47.884  INFO 26284 --- [  restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-04-28 19:06:47.890  INFO 26284 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-04-28 19:06:47.934  INFO 26284 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-04-28 19:06:48.655  WARN 26284 --- [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-04-28 19:06:48.919  INFO 26284 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-28 19:06:49.310  INFO 26284 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-04-28 19:06:49.315  INFO 26284 --- [  restartedMain] c.t.TransactionManagementApplication     : Started TransactionManagementApplication in 8.909 seconds (JVM running for 9.828)
2020-04-28 19:06:49.450  INFO 26284 --- [  restartedMain] c.t.service.impl.NotesServiceImpl        : Inside add note service
2020-04-28 19:06:49.464  INFO 26284 --- [  restartedMain] c.t.service.impl.NotesServiceImpl     : Successfully added new Note.

Output

As shown below in the diagram, after running the application, a transactionExampleDB schema will be created and notes and a users table will be created.


Users Table

Notes Table

Download Source Code

The full source code for this article can be found on below.
Download it here - Spring Boot Transaction Management

























































































































Recommendation for Top Popular Post :