This tutorial shows an example on declarative transaction management in Spring framework. You may also want to know about Spring Transaction or Declarative Transaction management in Spring. Here I will create annotation based Spring stand alone and Spring Boot applications to create Declarative Transaction Management example in Spring framework. Here I will use H2 in-memory database to create the example.
This example is completely Java based configuration and does not use any XMl configuration. I am also going to show you how to configure build.gradle script for gradle based project and pom.xml file if you are using maven as a build tool.
Related posts:
- 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 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 spring-declarative-transaction.
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-declarative-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/sql.
Table Script
Create below table SQL script – 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 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.
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.declarative.transaction")
package com.roytuts.spring.declarative.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.
I have created two beans – Datasource
and JdbcTemplate
, which are required to connect to the database and perform database operations.
Model Class
The below model class is used to represent each row of the table data to corresponding Java class attributes.
package com.roytuts.spring.declarative.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
I have created model class in the above but I need to map each row column with Java class attribute and for that we need Row Mapper class.
package com.roytuts.spring.declarative.transaction.row.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import com.roytuts.spring.declarative.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.
DAO Class
You need to interact with database, 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.declarative.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 com.roytuts.spring.declarative.transaction.model.Item;
import com.roytuts.spring.declarative.transaction.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()
method 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.
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.declarative.transaction.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.roytuts.spring.declarative.transaction.dao.ItemDao;
import com.roytuts.spring.declarative.transaction.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.
You can put put @Transactional
annotation at class level or at method level. If you put @Transactional
annotation at class level then it wraps all the method in a transaction scope. If you put @Transactional
annotation at method level then it works with the particular method on which it is applied.
You 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 you 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.
You can use @Transactional(readOnly=true)
for select queries and @Transactional
for insert and update queries because by default readOnly=false
. And for reading data you don’t need to use transaction as there is no chance of overwriting data.
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
.
Main Class
As you know for stand alone Spring application you need to have main class or Junit class to test your application. Here I am creating main class to test the application. I am also creating Spring Boot main class to test the application.
package com.roytuts.spring.declarative.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.declarative.transaction.model.Item;
import com.roytuts.spring.declarative.transaction.service.ItemService;
@SpringBootApplication
public class SpringDeclarativeTransactionApp implements CommandLineRunner {
@Autowired
private ItemService service;
public static void main(String[] args) {
SpringApplication.run(SpringDeclarativeTransactionApp.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();
}
}
The following class is for the stand alone Spring application.
package com.roytuts.spring.declarative.transaction;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.roytuts.spring.declarative.transaction.config.Config;
import com.roytuts.spring.declarative.transaction.model.Item;
import com.roytuts.spring.declarative.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.
You get the service class bean and you need to 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 you see that you 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.
superb…………its very clear