Spring @Transactional Example

By Arvind Rai, March 01, 2022
This page will walk through Spring @Transactional annotation example.
1. The @Transactional annotation describes a transaction attribute on an individual method or on a class. The @Transactional belongs to following package.
org.springframework.transaction.annotation 
2. When @Transactional annotation is declared at class level, it applies as a default to all methods of the declaring class and its subclasses. The @Transactional annotation does not apply to inherited methods of its ancestor classes. To make them transactional, we need to locally redeclare those methods in this class or its subclasses.
3. The @Transactional annotation commonly works with thread-bound transactions managed by a PlatformTransactionManager, exposing a transaction to all data access operations within the current execution thread. The transaction will not propagate newly started threads within the method.
4. In case of ReactiveTransactionManager, all participating data access operations need to execute within the same Reactor context in the same reactive pipeline.
5. To enable @Transactional annotation, use @EnableTransactionManagement with @Configuration. If we are using XML configuration, then use <tx:annotation-driven/> namespace.
6. The @Transactional at class level.
@Transactional(propagation = Propagation.NESTED, isolation = Isolation.SERIALIZABLE)
public class PersonDAO {
} 
The @Transactional at method level.
@Transactional(readOnly = true, timeout = 10)
public Person getPersonById(int pid) {
} 
7. Spring supports JPA @Transactional as well as Spring @Transactional. Both have the same role to inform container that a method is transactional.
JPA @Transactional is as below.
javax.transaction.Transactional 
Spring @Transactional is as below.
org.springframework.transaction.annotation.Transactional 
Here on this page we will discuss using Spring @Transactional annotation in detail.

Technologies Used

Find the technologies being used in our example.
1. Java 16
2. Spring 5.3.15
3. Spring Boot 2.6.3
4. MySQL 5.5

@Transactional Attributes

1. isolation : The Isolation type. It represents transaction isolation level. This is enum and its values are DEFAULT, READ_COMMITTED, READ_UNCOMMITTED, REPEATABLE_READ, SERIALIZABLE. The default isolation value is Isolation.DEFAULT.
@Transactional(isolation = Isolation.READ_COMMITTED)
public Person getPersonById(int pid) {
} 
The READ_COMMITTED constant indicates that dirty reads are prevented.
2. label : The String[] type. Label is used for transaction description. Default value is {}.
@Transactional(label = {"Transaction description for this method."})
public void updatePerson(Person person) {
} 
3. noRollbackFor : The Throwable array type. It accepts exception classes for which there must not be transaction rollback. Default is {}.
@Transactional(noRollbackFor = {NullPointerException.class, BrokenBarrierException.class})
public void deletePerson(int pid) {
} 
4. noRollbackForClassName : The String array type. It accepts exception class names for which there must not be transaction rollback. Default is {}.
@Transactional(noRollbackForClassName = {"NullPointerException", "BrokenBarrierException"})
public void deletePerson(int pid) {
} 
5. propagation : The Propagation enum type. We can assign the values as MANDATORY, NESTED, NEVER, NOT_SUPPORTED, REQUIRED, REQUIRES_NEW and SUPPORTS.
@Transactional(propagation = Propagation.NESTED)
public void updatePerson(Person person) {
} 
6. readOnly : The boolean type. If the value is true, it indicates that this transaction is read-only. The default value is false.
@Transactional(readOnly = true)	
public List<Person> getAllPersons() {
} 
7. rollbackFor : The Throwable array type. Specify exception classes which must cause a transaction rollback.
@Transactional(rollbackFor = {NullPointerException.class, BrokenBarrierException.class})
public void deletePerson(int pid) {
} 
8. rollbackForClassName : The String array type. Specify exception classes as string array which must cause a transaction rollback.
@Transactional(rollbackForClassName = {"NullPointerException", "BrokenBarrierException"})
public void deletePerson(int pid) {
} 
9. timeout : The int type. This is the timeout for this transaction in seconds.
@Transactional(timeout = 10)	
public List<Person> getAllPersons() {
} 
10. timeoutString : The String type. This is the timeout for this transaction in seconds.
@Transactional(timeoutString = "10")	
public List<Person> getAllPersons() {
} 
11. transactionManager : The String type. Specify the transaction manager that will match qualifier value or bean name of a specific TransactionManager bean definition.
@Transactional(transactionManager = "myTransactionManager")
@Repository
public class PersonDAO {
} 
Find the transaction manager bean.
@Bean
public HibernateTransactionManager myTransactionManager() {
     return new HibernateTransactionManager(sessionFactory());
} 
If transaction manager bean name is transactionManager, then @Transactional annotation picks this name by default.
In the below code, @Transactional annotation will pick a transaction manager with bean name transactionManager.
@Transactional
@Repository
public class PersonDAO {
} 
12. value : Alias for transactionManager attribute.
@Transactional("myTransactionManager")
@Repository
public class PersonDAO {
} 

Transaction Manager in XML Configuration

Here we create transactional manager using XML configuration with bean id transactionManager. The @Transactional annotation will automatically read it.
db-config.xml
<?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:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

	<context:property-placeholder location="classpath:database.properties"/> 
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" >
		<property name="url" value="${database.url}" />
		<property name="username" value="${database.root}" />
		<property name="password" value="${database.password}" />
	</bean>
	<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	    <property name="dataSource" ref="dataSource"/>
	    <property name="hibernateProperties">
			<props>
			   <prop key="hibernate.dialect">${hibernate.dialect}</prop>
			   <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
			   <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
			</props>
	    </property>
        <property name="packagesToScan" value="com.concretepage.entity"/> 
	</bean>
	<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
	     <constructor-arg name="sessionFactory" ref="sessionFactory"/>  
	</bean>
	<bean id="personDAO" class="com.concretepage.PersonDAO">
	    <constructor-arg name="hibernateTemplate" ref="hibernateTemplate"/>
	</bean>
	<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
	    <property name="sessionFactory" ref="sessionFactory" />
	</bean>	
	<tx:annotation-driven transaction-manager="transactionManager" /> 		
</beans> 
The <tx:annotation-driven/> enables annotation-driven transaction management capability. It means, it allows using @Transactional annotation in classes.
Now suppose transaction manager bean name is different.
<bean id="myTransactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean> 
Then we need to configure it specifically.
@Transactional("myTransactionManager")
@Repository
public class PersonDAO {
} 

Transaction Manager in @Configuration Class

Here we create transaction manager in @Configuration class. The @EnableTransactionManagement enables annotation-driven transaction management capability.
DBConfig.java
package com.concretepage;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@ComponentScan("com.concretepage") 
@PropertySource("classpath:database.properties")
public class DBConfig {
  @Autowired
  private Environment env;

  @Bean
  public HibernateTemplate hibernateTemplate() {
	return new HibernateTemplate(sessionFactory());
  }

  @Bean
  public SessionFactory sessionFactory() {
	LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();
	lsfb.setDataSource(getDataSource());
	lsfb.setPackagesToScan("com.concretepage.entity");
	lsfb.setHibernateProperties(hibernateProperties());
	try {
	  lsfb.afterPropertiesSet();
	} catch (IOException e) {
	  e.printStackTrace();
	}
	return lsfb.getObject();
  }

  @Bean
  public DataSource getDataSource() {
	BasicDataSource dataSource = new BasicDataSource();
	dataSource.setDriverClassName(env.getProperty("database.driver"));
	dataSource.setUrl(env.getProperty("database.url"));
	dataSource.setUsername(env.getProperty("database.root"));
	dataSource.setPassword(env.getProperty("database.password"));
	return dataSource;
  }

  @Bean
  public HibernateTransactionManager transactionManager() {
	return new HibernateTransactionManager(sessionFactory());
  }

  private Properties hibernateProperties() {
	Properties properties = new Properties();
	properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
	properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
	properties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
	return properties;
  }
} 
Look into the bean definition.
@Bean
public HibernateTransactionManager transactionManager() {
    return new HibernateTransactionManager(sessionFactory());
} 
Here bean name is method name i.e. transactionManager and hence @Transactional annotation will automatically pick up the transaction manager.

Using @Transactional

Using @Transactional annotation with no attributes, all the default values are applied automatically. If we want to change default value of any attribute, configure the desired value.
1. @Transactional at Class level
When we use @Transactional annotation at class level, it applies on all the methods of this class and its subclasses.
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
@Repository
public class PersonDAO {
  @Autowired
  private HibernateTemplate hibernateTemplate;

  public List<Person> getAllPersons() {
	return hibernateTemplate.loadAll(Person.class);
  }
  public void addPerson(Person person) {
	hibernateTemplate.save(person);
  }
  public void deletePerson(int pid) {
	hibernateTemplate.delete(getPersonById(pid));
  }
} 
2. @Transactional at Method level
When we use @Transactional annotation at methods, then only those methods are transactional and other method have no impact.
@Repository
public class PersonDAO {
  @Autowired
  private HibernateTemplate hibernateTemplate;

  public Person getPersonById(int pid) {
	return hibernateTemplate.get(Person.class, pid);
  }

  @Transactional(readOnly = true)  
  public List<Person> getAllPersons() {
	return hibernateTemplate.loadAll(Person.class);
  }
  public void addPerson(Person person) {
	hibernateTemplate.save(person);
  }
  @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
  public void updatePerson(Person person) {
	hibernateTemplate.update(person);
  }
  public void deletePerson(int pid) {
	hibernateTemplate.delete(getPersonById(pid));
  }
} 
3. @Transactional at Class level as well as Method level
Here we use @Transactional annotation at class level as well as method level.
PersonDAO.java
package com.concretepage;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.concretepage.entity.Person;

@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
@Repository
public class PersonDAO {
  @Autowired
  private HibernateTemplate hibernateTemplate;

  @Transactional(readOnly = true)  
  public Person getPersonById(int pid) {
	return hibernateTemplate.get(Person.class, pid);
  }

  @Transactional(readOnly = true, timeout = 20)  
  public List<Person> getAllPersons() {
	return hibernateTemplate.loadAll(Person.class);
  }
  
  public void addPerson(Person person) {
	hibernateTemplate.save(person);
  }
  
  public void updatePerson(Person person) {
	Person p = getPersonById(person.getPid());
	p.setName(person.getName());
	p.setCity(person.getCity());
	hibernateTemplate.update(p);
  }
  @Transactional(rollbackFor = {NullPointerException.class})  
  public void deletePerson(int pid) {
	hibernateTemplate.delete(getPersonById(pid));
  }
} 
Now find the complete example.
pom.xml
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.6.3</version>
	<relativePath />
</parent>
<properties>
	<java.version>16</java.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId> org.springframework.boot </groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-dbcp2</artifactId>
		<version>2.9.0</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.31</version>
	</dependency>
</dependencies> 
database.properties
database.driver = com.mysql.jdbc.Driver
database.url = jdbc:mysql://localhost:3306/concretepage
database.root = root
database.password = cp

hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.hbm2ddl.auto = update
hibernate.show_sql = true 
Database Table
CREATE TABLE `person` (
	`pid` BIGINT(5) NOT NULL,
	`name` VARCHAR(100) NOT NULL,
	`city` VARCHAR(100) NOT NULL,
	PRIMARY KEY (`pid`)
) 
Person.java
package com.concretepage.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity 
@Table(name= "person")
public class Person implements Serializable {
	private static final long serialVersionUID = 1L;
	@Id
	@Column(name="pid")
        private int pid;
	
	@Column(name = "name")
        private String name;
	
	@Column(name = "city")
        private String city;

        //Setters and Getters	
} 
SpringDemo.java
package com.concretepage;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.concretepage.entity.Person;

public class SpringDemo {
  public static void main(String... args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.register(DBConfig.class);
	ctx.refresh();
	PersonDAO personDAO = ctx.getBean(PersonDAO.class);

	// Add
	Person p1 = new Person();
	p1.setPid(100);
	p1.setName("Mahesh");
	p1.setCity("Varanasi");
	personDAO.addPerson(p1);

	Person p2 = new Person();
	p2.setPid(200);
	p2.setName("Ram");
	p2.setCity("Ayodhya");
	personDAO.addPerson(p2);

	// Get
	Person p = personDAO.getPersonById(200);
	System.out.println(p.getPid() + "|" + p.getName() + "|" + p.getCity());

	// Update
	p1.setName("Shiv");
	p1.setCity("Kashi");
	personDAO.updatePerson(p1);

	// Get All
	List<Person> list = personDAO.getAllPersons();
	list.forEach(ob -> System.out.println(ob.getPid() + "|" + ob.getName() + "|" + ob.getCity()));

	// Delete
	personDAO.deletePerson(100);
	personDAO.deletePerson(200);
  }
} 
Output
200|Ram|Ayodhya

100|Shiv|Kashi
200|Ram|Ayodhya 

Reference

Transaction Management

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us