Spring @Transactional Example
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
@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 { }
@Transactional
at method level.
@Transactional(readOnly = true, timeout = 10) public Person getPersonById(int pid) { }
@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
@Transactional
is as below.
org.springframework.transaction.annotation.Transactional
@Transactional
annotation in detail.
Contents
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 : TheIsolation
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) { }
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) { }
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) { }
@Transactional(noRollbackForClassName = {"NullPointerException", "BrokenBarrierException"}) public void deletePerson(int pid) { }
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) { }
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() { }
Throwable
array type. Specify exception classes which must cause a transaction rollback.
@Transactional(rollbackFor = {NullPointerException.class, BrokenBarrierException.class}) public void deletePerson(int pid) { }
String
array type. Specify exception classes as string array which must cause a transaction rollback.
@Transactional(rollbackForClassName = {"NullPointerException", "BrokenBarrierException"}) public void deletePerson(int pid) { }
int
type. This is the timeout for this transaction in seconds.
@Transactional(timeout = 10) public List<Person> getAllPersons() { }
String
type. This is the timeout for this transaction in seconds.
@Transactional(timeoutString = "10") public List<Person> getAllPersons() { }
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 { }
@Bean public HibernateTransactionManager myTransactionManager() { return new HibernateTransactionManager(sessionFactory()); }
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 { }
transactionManager
attribute.
@Transactional("myTransactionManager") @Repository public class PersonDAO { }
Transaction Manager in XML Configuration
Here we create transactional manager using XML configuration with bean idtransactionManager
. 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>
<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>
@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; } }
@Bean public HibernateTransactionManager transactionManager() { return new HibernateTransactionManager(sessionFactory()); }
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)); } }
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)); } }
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)); } }
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.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
CREATE TABLE `person` ( `pid` BIGINT(5) NOT NULL, `name` VARCHAR(100) NOT NULL, `city` VARCHAR(100) NOT NULL, PRIMARY KEY (`pid`) )
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 }
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); } }
200|Ram|Ayodhya 100|Shiv|Kashi 200|Ram|Ayodhya