Declarative Transaction Management Example in Spring

This tutorial shows an example on declarative transaction management in Spring. If you want to about Spring Transaction or Declarative Transaction management in Spring then you can read here. Here we will create annotation based Spring stand alone application to create Declarative Transaction Management example in Spring. Here we will use H2 in-memory database to create the example. This example is completely Java based configuration and does not use any XMl configuration.

Recommended reading:

Integrate H2 database with Spring Boot

Integrate H2 database with Spring

Spring MVC and JDBC CRUD Example with Zero XML

Programmatic Transaction Management in Spring

Prerequisites

JDK 1.8, Spring 5.1.7, H2 1.4.196, Gradle 4.10.2

Creating Annotation based Spring Application

Example with Source Code

Here we will create stand-alone application in Spring framework.

Creating Project

Create gradle based Spring application. The project structure looks to similar figure:

declarative transaction management in spring

Updating Build Script – build.script

Make sure to have the following dependencies in your build.gradle script.

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

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'io.spring.dependency-management'

sourceCompatibility = 1.8
targetCompatibility = 1.8

mainClassName = 'com.jeejava.spring.tx.Application'
jar {
    baseName = 'spring-declarative-transaction-management'
    version =  '0.0.1'
}

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.196")
}

You may or may not declare the main class in the above build script.

Creating SQL Scripts

As we are using H2 in-memory database to create our example. So we will create table and insert data using SQL scripts.

Creating 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`)
);

Creating 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);

Creating Spring Config Class

Create Spring configuration class to create datasource, jdbc template, configuration for base package etc.

package com.jeejava.spring.tx.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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
@ComponentScan(basePackages = "com.jeejava.spring.tx")
public class Config {

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

	@Bean
	public JdbcTemplate jdbcTemplate(DataSource dataSource) {

		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

		return jdbcTemplate;
	}

}

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

We have configured our base package to let Spring container know where our annotated classes reside.

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

Creating Model Class

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

package com.jeejava.spring.tx.model;

public class Item {

	private Long itemId;
	private String itemName;
	private String itemDesc;
	private Double itemPrice;

	public Long getItemId() {
		return itemId;
	}

	public void setItemId(Long itemId) {
		this.itemId = itemId;
	}

	public String getItemName() {
		return itemName;
	}

	public void setItemName(String itemName) {
		this.itemName = itemName;
	}

	public String getItemDesc() {
		return itemDesc;
	}

	public void setItemDesc(String itemDesc) {
		this.itemDesc = itemDesc;
	}

	public Double getItemPrice() {
		return itemPrice;
	}

	public void setItemPrice(Double itemPrice) {
		this.itemPrice = itemPrice;
	}

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

}

Creating Row Mapper Class

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

package com.jeejava.spring.tx.row.mapper;

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

import org.springframework.jdbc.core.RowMapper;

import com.jeejava.spring.tx.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 we are mapping the correct data type for each column value with Java class attribute.

Creating DAO Class

We will interact with database, so we 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.jeejava.spring.tx.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 com.jeejava.spring.tx.model.Item;
import com.jeejava.spring.tx.row.mapper.ItemRowMapper;

@Repository
public class ItemDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	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) {
		String sql = "INSERT INTO items(item_id, item_name, item_desc, item_price) VALUES (?,?, ?, ?);";
		jdbcTemplate.update(sql,
				new Object[] { item.getItemId(), item.getItemName(), item.getItemDesc(), item.getItemPrice() });
	}

	public void updateItem(Item item) {
		String sql = "UPDATE items SET item_name=?, item_desc=?, item_price=? WHERE item_id=?;";
		jdbcTemplate.update(sql,
				new Object[] { item.getItemName(), item.getItemDesc(), item.getItemPrice(), item.getItemId() });
	}

	public void deleteItem(Item item) throws Exception {
		String sql = "DELETE FROM items WHERE item_id=?;";
		jdbcTemplate.update(sql, new Object[] { item.getItemId() });
		throw new Exception();
	}

}

In the above DAO class, I have thrown new Exception() from deleteItem() even after deletion of an item to show you that the data gets rolled back on Exception.

Ideally you should code to interface to achieve loose coupling but here for showing an example I have used class as a DAO.

Creating Service Class

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

package com.jeejava.spring.tx.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.jeejava.spring.tx.dao.ItemDao;
import com.jeejava.spring.tx.model.Item;

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class ItemService {

	@Autowired
	private ItemDao itemDao;

	@Transactional(readOnly = true)
	public List<Item> getItems() {
		return itemDao.getItems();
	}

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

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

	@Transactional(rollbackFor = Exception.class)
	public void deleteItem(Item item) throws Exception {
		itemDao.deleteItem(item);
	}

}

Look in the above class, I have put @Transactional annotation on the service class to work with several DAO classes to make a unit of work for service class. Because you may have several DAO classes, injected into a Service, that need to work together in a single transaction.

Transactional Annotation

This @Transactional annotation is for declarative transaction management in Spring application.

We can put put @Transactional annotation at class level or at method level. If we put @Transactional annotation at class level then it wraps all the method in a transaction scope. If we put @Transactional annotation at method level then it works with the particular method on which it is applied. We can also put @Transactional annotation at class and method levels at the same time and in this case @Transactional annotation will be applied for all the methods with similar behavior except the methods for which we have applied @Transactional annotation at method level will have different behavior.

@Transactional annotation has several properties such as readOnly, isolation, propagation, rollbackFor, noRollbackFor etc. These properties can be used to control the behavior and communication with other transactions.

We can use @Transactional(readOnly=true) for select queries and @Transactional for insert and update queries because by default readOnly=false.

Another two important properties are rollbackFor and noRollbackFor – used for if a class throws exception the transaction will be rolled back for that exception class and rollback will not happen for that exception class respectively. I have written deleteItem() to throw an Exception so that I can show you how the data is rolled back on Exception.

Creating Main Class

As we know this is a stand alone Spring application, so we need to have main class or Junit class to test our application. Here I am creating main class to test our application.

package com.jeejava.spring.tx;

import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.jeejava.spring.tx.config.Config;
import com.jeejava.spring.tx.model.Item;
import com.jeejava.spring.tx.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 we get the bean from annotation based Java classes. We have loaded the Config.class because this class configures everything required for the application.

We get the service class bean and we call the deleteItem() and getItems() methods to delete an item and show all items, respectively.

Testing the Application

Run the above main class you will see the following 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 was not deleted.
Transaction rolled back due to Exception.

So from the above output we see that we had initially six items and an item was not deleted using deleteItem() method because of exception.

So if you again call getItems() method you will see six items will be displayed on the console.

Hope you got an idea on Declarative Transaction Management Example in Spring.

Source Code

You can download source code.

Thanks for your reading.

Declarative Transaction Management Example in Spring

0 thoughts on “Declarative Transaction Management Example in Spring

Leave a Reply

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

Scroll to top