Spring Transaction Management Example Using Spring Boot

In this tutorial, we will see a Spring transaction management example using spring boot. First, we will see some basics about Spring Transaction Management then we will see a complete example.

We are going to discuss the below points.

  1. What is a transaction?
  2. Different ways to define Transaction management in Spring.
  3. Use of @Transactional annotation.
  4. @Transactional Attributes.
  5. Transaction Management example using spring boot.

We will see how to use @Transactional annotation, and what will happen if we don’t use @Transactional annotation. We will also cover what are attributes defined for @Transactional annotation and what it does.

Note – We should not @Transactional annotation with the private method.

See Hibernate Transaction Management example.

Spring Transaction management basics.

The transaction can be defined with ACID properties.

Atomicity – All success or none.
Consistency – Database constraints should not be violated.
Isolation – One transaction should not affect another one.
Durability – It should in the Database after commit.

Three are two ways to define Transaction management.

  1. Programmatic transaction management.
  2. Declarative transaction management.

Programmatic transaction management – The Spring Framework provides two ways of programmatic transaction management.

  • Using the TransactionTemplate
  • Using PlatformTransactionManager implementation directly

Using TransactionTemplate –

@Configuration
@EnableJpaRepositories(basePackages = "com.javatute.repository")
public class JpaConfig {
    private final TransactionTemplate transactionTemplate;
    public JpaConfig(PlatformTransactionManager transactionManager) {
        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.PROPAGATION_REQUIRED);
        this.transactionTemplate.setTimeout(30);
    }
}

Using PlateFormTransactionManager directly –

    public void someMethod(PlatformTransactionManager transactionManager){
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setName("SomeTxName");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // business logic
        }
        catch (Exception ex) {
            transactionManager.rollback(status);
            throw ex;
        }
        transactionManager.commit(status);
    }

Note – It is recommended to use TransactionTemplate for programmatic transaction management. The second approach is similar to using the JTA UserTransaction API. See more details in docs.

In the case of Programmatic transaction management, we need to write some extra code to manage the transaction. For example, we need to write code to create a transaction, begin the transaction and commit/rollback the transaction.

Let’s see the sample code.

Transaction transactionRef = entityManager.getTransaction()                  
try {  
   transactionRef.begin();                   
   // business logic                   
   transactionRef .commit();  
}                  
catch(Exception e) {                     
   transactionRef.rollback();  
   e.printStackTrace();                 
}

Declarative transaction management – No need to write extra code for getting a transaction, we can use annotations or XML-based approach to manage the transactions and we can avoid unnecessary code. If we use annotation based approach we need to use @Transactional and if we use the XML-based approach we need to configure DataSourceTransactionManager or any other transaction manager in XML as a bean. In this article and the next upcoming article, we will see the annotation based approach.

Sample code for annotation-based declarative Spring transaction management.

    @Transactional
    public Book findOneString objectId) {
        return repository.findOne(objectId);
    }

Sample code for XML-based declarative Spring transaction management.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>	
</beans>

Use of Spring @Transactional annotation

Till now we have seen ACID properties and different approaches to implement Spring transaction management. Now we will see how to implement declarative transaction management using annotation.

@Transactional is the key annotation that is responsible for declarative spring transaction management. Apart from this annotation, we use @EnableTransactionManagement annotation.

Let’s see what is the advantage of using @Transactional annotation what happens if we don’t use @Transactional annotation. We will also see how to use @Transactional annotation.

@Transactional annotation used with class, interface, or method.

Let’s see a small code snippet that would explain the effect of @Transactional annotation.

Consider you have a class BookServiceImpl.java where you have logic to save Book entity.

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService{
	
@Autowired
private BookRepository bookRepository;

public Book saveBook(Book book) {
	Book book1 = bookRepository.save(book);
	int a = 10/0;
	System.out.println(a);
	return book1;
    } 
}

Observe the saveBook() method. In the above code snippet line number 9 will throw ArithmeticException but before that bookRepository.save(book)will get exected. Now the question comes would we have records in the database or not? In the above scenario even if we have an exception in our code, we would have a record in the Database.

That we might don’t want this to happen. If we have something wrong with our code, data should not persist in the database. Now just modify the code snippet as below, use @Transactional annotation with saveBook() method.

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService{
	
@Autowired
private BookRepository bookRepository;

@Transactional
public Book saveBook(Book book) {
	Book book1 = bookRepository.save(book);
	int a = 10/0;
	System.out.println(a);
	return book1;
 } 
}

Since we are using @Transactional annotation with saveBook() method and we have ArithmeticException at code int a = 10/0, our transaction will get rollbacked and we will not have any record in the database.

This is the very example that explains what is the benefit of @Transactional annotation. Although @Transactional annotation does a lot of other stuff which we will cover later in separate tutorials.

Understanding Spring @Transactional Attributes.

So far we have seen the benefits of using @Transactional annotation. Let’s see what properties have been defined for the @Transactional annotation. We have different properties/attributes with @Transactional annotation so that we can have more control over our transactions. When we say more control over the transaction, what does it mean?

The @Transactional annotation has a different attribute and corresponding value. We can use those attributes and customize our transactions. For example –

  • if we use @Transactional(readOnly=true) with the method, we will not able to update records in the same transaction.
  • We use @Transactinal(readOnly = true) if our method performs an only search or retrieve operation. Similarly, we have @Transactinal(timeout = 100), which means if any transaction doesn’t complete in 100 seconds, it will throw a time-out error(see an example here).

Let’s see the attributes of @Transactional annotation.

@Transactional(isolation = Isolation.DEFAULT,
propagation=Propagation.REQUIRES_NEW,
readOnly=true,
noRollbackFor =ArithmeticException.class,
timeout = 30000,
value="txManager2",
rollbackFor = { Exception.class },
rollbackForClassName = {"Exception"},
noRollbackForClassName={"Exception"})

So here we have a sample code that elaborates what are the possible attributes for @Transactional. Another point here isolation and propagation attribute has different values. Now it seems a little bit tricky. Yes until unless we are not sure about attribute names and possible values(and what exactly it does, it is not easy to implement in real time development).

Let’s see all attributes/properties one by one.

Propagation – propagation can have different possible values as below.

Propagation.REQUIRED – Support a current transaction, and create a new one if none exists.
Propagation.REQUIRES_NEW – Always create a new transaction and suspend the current transaction if it already exists.
Propagation.MANDATORY – Support a current transaction, and throw an exception if none exists.
Propagation.NESTED – Execute within a nested transaction if a current transaction exists.
Propagation.NEVER – Execute non-transactionally, throw an exception if a transaction exists.
Propagation.NOT_SUPPORTED – Execute non-transactionally, suspend the current transaction if one exists.
Propagation.SUPPORTS – Support a current transaction, execute non-transactionally if none exists.

Note – Propagation.REQUIRED and Propagation.REQUIRES_NEW is frequently used in real-time development. See an example here. Default Propagation value is Propagation.REQUIRED

Isolation – isolation can have different possible values as below.

Isolation.READ_UNCOMMITTED – It allows dirty reads, non-repeatable reads, and phantom reads.
Isolation.READ_COMMITTED – Dirty reads are prevented, allows non-repeatable and phantom reads.
Isolation.REPEATABLE_READ – Dirty reads and non-repeatable prevented, phantom reads allowed.
Isolation.SERIALIZABLE – Dirty reads, non-repeatable reads, and phantom reads are prevented.

Note – Default isolation value is Isolation.DEFAULT.

rollbackFor – We can define zero, one, or multiple exceptions for which we want our transaction to be rollbacked. We have a separate post where we can find more details.

@Transactional(rollbackFor = {RuntimeException.class})

noRollbackFor – We can define zero, one or multiple exceptions for which we don’t want our transaction to be rollbacked. We have a separate post where we can find more details.

@Transactional(noRollbackFor = {RuntimeException.class})

rollbackForClassName – We can define zero, one or multiple exceptions as String for which we want our transaction to be rollbacked. We have a separate post where we can find more details.

@Transactional(rollbackForClassName = {“NullPointerException”})

noRollbackForClassName – We can define zero, one or multiple exceptions as String for which we don’t want our transaction to be rollbacked. We have a separate post where we can find more details.

@Transactional(noRollbackForClassName = {“NullPointerException”})

readOnly – Its value can be true or false. Go through this post for more details.

@Transactional(readOnly = false)

Spring Transaction Management example using spring boot and MySql.

Create a maven project using eclipse or Intellij idea and add the dependency.

<?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>springtransactionexample</groupId>
    <artifactId>springtransactionexample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</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>
        </dependency>
    </dependencies>

</project>

Let maven download all necessary jars. Once it is done we will be able to see the maven dependency folder which contains different jar files.

Directory structure

Spring Transactional example using Spring Boot

Define entity class i.e Book.java

Book.java

package com.javatute.entity;

import javax.persistence.*;

@Entity
@Table(name = "book")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int bookId;

    @Column(name = "book_name")
    private String bookName;

    @Column(name = "auther_name")
    private String autherName;
    @Column(name = "price")
    private int price;

    public String getAutherName() {
        return autherName;
    }

    public void setAutherName(String autherName) {
        this.autherName = autherName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }
}

Define repository interface extending CrudRepository.

BookRepository.java

package com.javatute.repository;

import com.javatute.entity.Book;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.io.Serializable;

@Repository
public interface BookRepository extends CrudRepository<Book, Serializable> {
    public Book findByBookId(int bookId);

}

Define service interface i.e BookService.java

BookService.java

package com.javatute.service;

import com.javatute.entity.Book;
import org.springframework.stereotype.Component;

@Component
public interface BookService {
    public Book findByBookId(int bookId);

    public Book saveBook(Book book);
}

Define service implementation class.

BookServiceImpl.java

package com.javatute.serviceimpl;

import com.javatute.entity.Book;
import com.javatute.repository.BookRepository;
import com.javatute.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService {

    @Autowired
    private BookRepository bookRepository;

    public Book findByBookId(int bookId) {
        Book book = bookRepository.findByBookId(bookId);
        return book;
    }

    @Transactional
    public Book saveBook(Book book) {
        Book book1 = bookRepository.save(book);
        return book1;
    }
}

Define the controller class or endpoint.

BookController.java

package com.javatute.controller;

import com.javatute.entity.Book;
import com.javatute.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    private BookService bookService;

    @RequestMapping(value = "/getBook", method = RequestMethod.GET)
    @ResponseBody
    public Book getBookDetails(int bookId) {
        Book bookResponse = bookService.findByBookId(bookId);
        return bookResponse;
    }

    @RequestMapping(value = "/savebook", method = RequestMethod.POST)
    @ResponseBody
    public Book saveBook(@RequestBody Book book) {
        Book bookResponse = bookService.saveBook(book);
        return bookResponse;
    }
}

Define JpaConfig class.

package com.javatute.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories(basePackages = "com.javatute.repository")
public class JpaConfig {

}

Define application.properties file

application.properties for Oracle DataBase

# Connection url for the database
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=SYSTEM
spring.datasource.password=oracle1
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
 
spring.datasource.testWhileIdle = true
spring.datasource.validationQuery = SELECT 1
 
# Show or not log for each sql query
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto =update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
server.port = 9091
useBuiltinConverters: true
 mapNulls: true
logging.level.org.springframework.transaction.interceptor=TRACE

application.properties for MySql database

spring.datasource.url=jdbc:mysql://localhost:3306/springbootcrudexample
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
server.port = 9091

Define SpringMain.java

package com.javatute.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = "com.javatute.*")
@EntityScan("com.javatute.*")
public class SpringMain {
    public static void main(String[] args) {
        SpringApplication.run(SpringMain.class, args);
    }
}

Run the main class and test the example.

First, we will save one book record using postman.

Spring transaction management example using spring boot

Now we have one record in DB. Let’s perform get operation

Although we have already discussed this, just for reminding In the above example in BookServiceImpl.java, we are using the @Transactional annotation with the saveBook() method. If we don’t use @Transactional annotation it will work fine we will have a record in DB. But if we modify BookServiceImpl.java something like the below, then we would not have any record in the database. Our transaction will get rollbacked because of ArithmeticException

@Transactional
public Book saveBook(Book book) {
	Book book1 = bookRepository.save(book);
	int a = 10/0;
	System.out.println(a);
	return book1;
}

Note – It can be ArithmeticException, NullPointerException, or any RuntimeException.

That’s all about Spring transaction management example using spring boot.

Few basic questions related to spring transaction management.

1. Can we use @Transactional annotation in the controller?

We can. But we should avoid using @Transactional annotation in the controller, instead we should use it in Service classes.

2. What are the default @Transactional settings?

The propagation setting is PROPAGATION_REQUIRED. The isolation level is ISOLATION_DEFAULT. The transaction is read/write.

Download the source code from Github.

Other @Transactional related tutorials.

Spring Data JPA tutorials.

See Docs here.

Summary – We have seen the Spring Transaction management example using Spring Boot. We covered @Transactinal attributes as well.