Programmatic Transaction Management Example in Spring

Last Updated on 23rd September 2020 at 06:26 pm

In this tutorial I will show an example on programmatic transaction management in Spring. If you want to read about Spring Transaction or Programmatic Transaction management then you can read Transaction management in Spring.

Here I will show annotation based Spring stand alone application approach and Spring Boot application to create Programmatic Transaction Management example in Spring. I am also going to give you the pom.xml file if you are using maven as a build tool.

Here I am going to use H2 in-memory database to create the example. This example is completely Java based configuration and does not use any XMl configuration.

Related posts:

Prerequisites

JDK at least 1.8, Spring 5.2.9, H2 1.4.200, Gradle 4.10.2/5.6.1/6.5.1, Spring Boot 2.3.4

Creating Annotation based Spring Application

Project Setup

Create gradle based Spring application. The project name is programmatic-transaction-management.

If you want to work with Spring Boot application then you can use the below build.gradle script.

buildscript {
	ext {
		springBootVersion = '2.3.4.RELEASE'
	}
	
    repositories {
    	mavenCentral()
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id 'java-library'
    id 'org.springframework.boot' version "${springBootVersion}"
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter:${springBootVersion}")
	implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}")
	
	runtime("com.h2database:h2:1.4.200")
}

If you want to work with standalone Spring application then you can use the below build.gradle script.

buildscript {
	ext {
		springVersion = '5.2.9.RELEASE'
	}
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    dependencies {
    	classpath("io.spring.gradle:dependency-management-plugin:1.0.9.RELEASE")
    }
}

plugins {
    id 'java-library'
    id 'io.spring.dependency-management' version "1.0.9.RELEASE"
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
	mavenLocal()
    mavenCentral()
}

dependencies {
	compile("org.springframework:spring-core:${springVersion}")
	compile("org.springframework:spring-beans:${springVersion}")
	compile("org.springframework:spring-context:${springVersion}")
	compile("org.springframework:spring-jdbc:${springVersion}")
	compile("org.springframework:spring-tx:${springVersion}")
	runtime("com.h2database:h2:1.4.200")
}

If you are using maven based project then you can use below pom.xml file:

<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>com.roytuts</groupId>
	<artifactId>spring-programmatic-transaction</artifactId>
	<version>0.0.1-SNAPSHOT</version>

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

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>1.4.200</version>
		</dependency>
	</dependencies>

    <build>
        <plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>at least 8</source>
					<target>at least 8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

SQL Scripts

As I am using H2 in-memory database to create example, so I will create table and insert data some sample data using SQL scripts. These SQL scripts are kept under classpath directory src/main/resources.

Table Script

Create below table SQL script – create-table.sql and put it under src/main/resources/sql directory.

CREATE TABLE `item` (
  `item_id` int(10) NOT NULL AUTO_INCREMENT,
  `item_name` varchar(45) NOT NULL,
  `item_desc` text,
  `item_price` double NOT NULL DEFAULT '0',
  PRIMARY KEY (`item_id`)
);

Insert Script

Create below insert-data.sql script and put it under src/main/resources/sql directory.

insert into `item`(`item_id`,`item_name`,`item_desc`,`item_price`)
values (1,'CD','CD is a compact disk',100),
(2,'DVD','DVD is larger than CD in size',150),
(3,'ABC','ABC test description',24),
(4,'XYZ','XYZ test description',25.32),
(5,'CD Player','CD player is used to play CD',30.02),
(6,'New Item1','New Item1 Desc',125);

Spring Config Class

Create Spring configuration class to create datasource, jdbc template, configuration for base package etc. This configuration class can be used both for standalone Spring application and Spring Boot application.

package com.roytuts.spring.programmatic.transaction.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
public class Config {

    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2) // .H2 or .DERBY, etc.
                .addScript("sql/table.sql").addScript("sql/data.sql").build();
        return db;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate;
    }

}

In the above class I have used annotation @Configuration to indicate it is a configuration class.

If you are using standalone Spring application then you need to put @ComponentScan annotation with your base package to let Spring container know where our annotated classes reside. For example,

@ComponentScan(basePackages = "com.roytuts.spring.programmatic.transaction")

I have created two beans – Datasource and JdbcTemplate, which are required to perform database operations.

Model Class

I will create a model class to represent each row of the table data to corresponding Java class attributes.

package com.roytuts.spring.programmatic.transaction.model;

public class Item {

    private Long itemId;

    private String itemName;

    private String itemDesc;

    private Double itemPrice;

    // getters and setters

    @Override
    public String toString() {
        return "Item [itemId=" + itemId + ", itemName=" + itemName + ", itemDesc=" + itemDesc + ", itemPrice="
                + itemPrice + "]";
    }

}

Row Mapper Class

I have created model class in the above but I need to map each row column with Java class attribute and for that I have created the following Row Mapper class.

package com.roytuts.spring.programmatic.transaction.row.mapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.roytuts.spring.programmatic.transaction.model.Item;

public class ItemRowMapper implements RowMapper<Item> {

    @Override
    public Item mapRow(ResultSet rs, int rowNum) throws SQLException {
        Item item = new Item();
        item.setItemId(rs.getLong("item_id"));
        item.setItemName(rs.getString("item_name"));
        item.setItemDesc(rs.getString("item_desc"));
        item.setItemPrice(rs.getDouble("item_price"));
        return item;
    }

}

Notice in the above class I am mapping the correct data type for each column value with Java class attribute.

Programmatic Transactions

There are two types of programmatic transactions – Platform Transaction Manager and Transaction Template.

Using Platform Transaction Manager

The DataSourceTransactionManager bean, which you need to have declared in the Config.java file, is of type of PlatformTransactionManager. Hence you can directly use to handle programmatic transactions.

Config.java

Therefore modify the existing Config.java class to add the following bean declaration:

...
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
	return new DataSourceTransactionManager(dataSource);
}
...

DAO Class

Now you need to perform database operations, so you need to create DAO class or Repository class for performing database operations.

Create a class with @Repository annotation to auto-pickup by Spring container. You may also use @Component annotation if you think @Repository is not relevant on this DAO class.

package com.roytuts.spring.programmatic.transaction.dao;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.roytuts.spring.programmatic.transaction.model.Item;
import com.roytuts.spring.programmatic.transaction.row.mapper.ItemRowMapper;

@Repository
public class ItemDaoPfTx {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    PlatformTransactionManager transactionManager;

    public List<Item> getItems() {
        String sql = "SELECT * FROM item";
        List<Item> items = new ArrayList<>();
        items = jdbcTemplate.query(sql, new ItemRowMapper());
        return items;
    }

    public void addItem(Item item) {
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            String sql = "INSERT INTO item(item_id, item_name, item_desc, item_price) VALUES (?, ?, ?, ?);";
            jdbcTemplate.update(sql,
                    new Object[] { item.getItemId(), item.getItemName(), item.getItemDesc(), item.getItemPrice() });
            transactionManager.commit(transactionStatus);
        } catch (Exception ex) {
            transactionManager.rollback(transactionStatus);
        }
    }

    public void updateItem(Item item) {
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            String sql = "UPDATE item SET item_name=?, item_desc=?, item_price=? WHERE item_id=?;";
            jdbcTemplate.update(sql,
                    new Object[] { item.getItemName(), item.getItemDesc(), item.getItemPrice(), item.getItemId() });
            transactionManager.commit(transactionStatus);
        } catch (Exception ex) {
            transactionManager.rollback(transactionStatus);
        }
    }

    public void deleteItem(Item item) throws Exception {
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition(
                TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            String sql = "DELETE FROM item WHERE item_id=?;";
            jdbcTemplate.update(sql, new Object[] { item.getItemId() });
            transactionManager.commit(transactionStatus);
        } catch (Exception ex) {
            ex.printStackTrace();
            transactionManager.rollback(transactionStatus);
        }
    }

}

In the above DAO class that uses PlatformTransactionManager, the Transaction Management code can be seen in addItem(), updateItem() and deleteItem() methods. So lets understand:

Step 1 is to create an object of DefaultTransactionDefinition. This object exposes various setter methods to configure the transaction accordingly.

Step 2 is to to create the TransactionStatus object using the transaction definition we created in Step 1. Eventually this TransactionStatus will be used to commit or rollback the transaction like platformTransactionManager.commit(transactionStatus); or platformTransactionManager.rollback(transactionStatus);

Service Class

Create below Spring service class to perform business logic. I annotated the class using @Service to indicate it as a service class.

package com.roytuts.spring.programmatic.transaction.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.roytuts.spring.programmatic.transaction.dao.ItemDaoPfTx;
import com.roytuts.spring.programmatic.transaction.model.Item;

@Service
public class ItemService {

    @Autowired
    private ItemDaoPfTx itemDao;

    public List<Item> getItems() {
        return itemDao.getItems();
    }

    public void addItem(Item item) {
        itemDao.addItem(item);
    }

    public void updateItem(Item item) {
        itemDao.updateItem(item);
    }

    public void deleteItem(Item item) throws Exception {
        itemDao.deleteItem(item);
    }

}

Here in the above service class I did not apply any transaction because I am managing transaction programmatically.

Main Class

As this is a stand alone Spring application, so you need to have main class or Junit class to test the application. Here I am creating main class to test our application. For Spring Boot application use the following class.

package com.roytuts.spring.programmatic.transaction;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.roytuts.spring.programmatic.transaction.model.Item;
import com.roytuts.spring.programmatic.transaction.service.ItemService;

@SpringBootApplication
public class SpringProgrammaticTransactionApp implements CommandLineRunner {

    @Autowired
    private ItemService service;

    public static void main(String[] args) {
        SpringApplication.run(SpringProgrammaticTransactionApp.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("----Available Items----");

        List<Item> items = service.getItems();
        items.forEach(i -> System.out.println(i));

        System.out.println();

        try {
            Item delItem = new Item();
            delItem.setItemId(6l);
            service.deleteItem(delItem);

            System.out.println("Item Successfully Deleted.");
        } catch (Exception ex) {
            System.out.println("Item was not deleted.");
            System.out.println("Transaction rolled back due to Exception.");
        }

        System.out.println();
    }

}

For standalone Spring application you can use the below class.

package com.roytuts.spring.programmatic.transaction;

import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.roytuts.spring.programmatic.transaction.config.Config;
import com.roytuts.spring.programmatic.transaction.model.Item;
import com.roytuts.spring.programmatic.transaction.service.ItemService;

public class Application {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(Config.class);
		context.refresh();
		
		ItemService service = context.getBean(ItemService.class);
		
		System.out.println("----Available Items----");
		
		List<Item> items = service.getItems();
		items.forEach(i -> System.out.println(i));
		
		System.out.println();
		
		try {
			Item delItem = new Item();
			delItem.setItemId(6l);
			service.deleteItem(delItem);
			System.out.println("Item Successfully Deleted.");
		} catch (Exception ex) {
			System.out.println("Item was not deleted.");
			System.out.println("Transaction rolled back due to Exception.");
		}
		
		System.out.println();
		
		context.close();
	}
	
}

Notice in the above class, how do I get the bean from annotation based Java classes. I have loaded the Config.class because this class configures everything required for the application.

Testing the Application

Run the above class you will see below output on the Eclipse console.

----Available Items----
Item [itemId=1, itemName=CD, itemDesc=CD is a compact disk, itemPrice=100.0]
Item [itemId=2, itemName=DVD, itemDesc=DVD is larger than CD in size, itemPrice=150.0]
Item [itemId=3, itemName=ABC, itemDesc=ABC test description, itemPrice=24.0]
Item [itemId=4, itemName=XYZ, itemDesc=XYZ test description, itemPrice=25.32]
Item [itemId=5, itemName=CD Player, itemDesc=CD player is used to play CD, itemPrice=30.02]
Item [itemId=6, itemName=New Item1, itemDesc=New Item1 Desc, itemPrice=125.0]

Item Successfully Deleted.

So in the above output, it is clear that there is no exception occurred and new item was added successfully. One item was successfully deleted also.

Using Transaction Template

Spring TransactionTemplate is similar to JdbcTemplate and is used to abstract away the boilerplate code from the user. It provides simple callback methods which are automatically wrapped in a transaction. It also exposes direct setter methods for configuring various properties of transactions like setRollbackOnly() etc. The Spring’s TransactionTemplate is initialized by providing a reference of DataSourceTransactionManager bean.

Config.java

You need to add TransactionTemplate bean to our existing config class. Do not remove anything from the existing Config.java class.

...
@Bean
public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
	return new TransactionTemplate(transactionManager);
}
...

I defined bean DataSourceTransactionManager for the Platform Transaction Manager earlier and it is required for our Transaction Template as you see in the above, it refers to.

DAO Class

Create below DAO class to have our TransactionTemplate.

Using TransactionTemplate, you do not need to create a TransactionDefinition and Transaction object. The TransactionTemplate provides a callback method called execute where in you write our business logic that you want to wrap in a transaction. There are two types of callback methods that you can use to wrap our code i.e TransactionCallbackWithoutResult and TransactionCallback(T).

package com.roytuts.spring.programmatic.transaction.dao;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.roytuts.spring.programmatic.transaction.model.Item;
import com.roytuts.spring.programmatic.transaction.row.mapper.ItemRowMapper;

@Repository
public class ItemDaoTxTemp {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionTemplate transactionTemplate;

    public List<Item> getItems() {
        String sql = "SELECT * FROM item";
        List<Item> items = new ArrayList<>();
        items = jdbcTemplate.query(sql, new ItemRowMapper());
        return items;
    }

    public void addItem(final Item item) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    String sql = "INSERT INTO item(item_id, item_name, item_desc, item_price) VALUES (?, ?, ?, ?);";
                    jdbcTemplate.update(sql, new Object[] { item.getItemId(), item.getItemName(), item.getItemDesc(),
                            item.getItemPrice() });
                } catch (Exception e) {
                    transactionStatus.setRollbackOnly();
                }
            }
        });
    }

    public void updateItem(final Item item) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    String sql = "UPDATE item SET item_name=?, item_desc=?, item_price=? WHERE item_id=?;";
                    jdbcTemplate.update(sql, new Object[] { item.getItemName(), item.getItemDesc(), item.getItemPrice(),
                            item.getItemId() });
                } catch (Exception e) {
                    transactionStatus.setRollbackOnly();
                }
            }
        });
    }

    public void deleteItem(final Item item) throws Exception {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    String sql = "DELETE FROM item WHERE item_id=?;";
                    jdbcTemplate.update(sql, new Object[] { item.getItemId() });
                } catch (Exception e) {
                    transactionStatus.setRollbackOnly();
                }
            }
        });
    }

}

Service Class

Modify the existing service that you created during Platform Transaction Manager as shown below and rest of the code remains same.

@Autowired
// private ItemDaoPfTx itemDao;
private ItemDaoTxTemp itemDao;

Running the Application

Now run the main class that you created during Platform Transaction Manager, you will see the below output, which is same as earlier.

----Available Items----
Item [itemId=1, itemName=CD, itemDesc=CD is a compact disk, itemPrice=100.0]
Item [itemId=2, itemName=DVD, itemDesc=DVD is larger than CD in size, itemPrice=150.0]
Item [itemId=3, itemName=ABC, itemDesc=ABC test description, itemPrice=24.0]
Item [itemId=4, itemName=XYZ, itemDesc=XYZ test description, itemPrice=25.32]
Item [itemId=5, itemName=CD Player, itemDesc=CD player is used to play CD, itemPrice=30.02]
Item [itemId=6, itemName=New Item1, itemDesc=New Item1 Desc, itemPrice=125.0]

Item Successfully Deleted.

That’s all. Hope you got an idea on Pragrammatic Transaction Example in Spring.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *