Spring Boot Transaction Management + JPA(2024)
In this tutorial, we will understand transaction management by using the @transactional annotation by implementing the spring boot + JPA example.
Spring Boot Transaction Management:
- Transaction Management Example + JPA
- Transaction Propagation
- Transaction Interview Questions
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.
- Atomicity - All success or none.
- Consistency - Integrity constraints must be maintained in such a way that the database is consistent before and after the transaction.
- Isolation - One transaction should not impact on another transaction.
- 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.
-
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; }
-
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 Transaction Management, different types of Transaction 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.
- UserService to create new user.
- NotesService to add Note.
- 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