Java Articles

Advertisement

Spring @Transactional Annotation Example

by Ram Satish

Share

Spring @Transactional annotation allows one to execute tests within test-managed transactions.
Annotating a test method with @Transactional causes the test to be run within a transaction. By default, the transaction will be automatically rolled back after completion of the test. If a test class is annotated with @Transactional, each test method within that class hierarchy will be run within a transaction. Test methods that are not annotated with @Transactional at the class or method level will not be run within a transaction.

Spring @Transactional Flow

Here is the flow when a test is annotated with @Transactional.

Test Transaction Flow
Test Transaction Flow

We will demonstrate the transaction flow using test SpringTransactionalAnnotationExample.

Advertisement

Few points to be noted about our example.

  1. beforeTransactionInitData() is a @BeforeTransaction method - We create the schema here and then add a default 'admin' row to employee table
  2. In @Before test method printTestName() – Prints test name
  3. rollbackDataByDefault() – This is a test method, annotated with @Transactional. In this case, we add a new employee. By default a test transaction is flagged for rollback so whatever operations we perform in test will automatically get rolled back during the end transaction phase
  4. afterTransactionVerifyRows is an @AfterTransaction method where we assert the rows expected based on the test case
  5. In commitData() test, we explicitly mark the transaction for commit overriding the default rollback behavior.
  6. rollbackFalse() is same as commitData() except that we use @Rollback(false) to indicate that the transaction is marked for commit

SpringTransactionalAnnotationExample:

package com.javarticles.spring;

import static org.junit.Assert.assertEquals;

import java.util.List;

import javax.sql.DataSource;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TestTransaction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SpringTransactionalAnnotationExample {
@Rule
public TestName testName = new TestName();

@Autowired
private JdbcTemplate jdbcTemplate;

@Autowired
private ApplicationContext applicationContext;

@BeforeTransaction
public void beforeTransactionInitData() {
System.out.println("Before Transaction");
executeSqlScript("classpath:/com/javarticles/spring/schema.sql");
jdbcTemplate.update("insert into employee(name) values ('admin')");
}

@Before
public void printTestName() {
System.out.println("Test: " + testName.getMethodName());
}

@Test
@Transactional
public void rollbackDataByDefault() {
System.out.println("Is flagged for rollback? "
+ TestTransaction.isFlaggedForRollback());
jdbcTemplate.update("insert into employee(name) values ('Joe')");
}

@Test
@Transactional
public void commitData() {
System.out.println("Is transaction active? "
+ TestTransaction.isActive());
jdbcTemplate.update("insert into employee(name) values ('Joe')");
TestTransaction.flagForCommit();
}

@Test
@Transactional
@Rollback(false)
public void rollbackFalse() {
System.out.println("Is flagged for rollback? "
+ TestTransaction.isFlaggedForRollback());
jdbcTemplate.update("insert into employee(name) values ('Joe')");
}

@AfterTransaction
public void afterTransactionVerifyRows() {
String methodName = testName.getMethodName();
if ("commitData".equals(methodName)) {
assertEquals(2, countEmpRows());
} else if ("rollbackDataByDefault".equals(methodName)) {
assertEquals(1, countEmpRows());
} else if ("rollbackFalse".equals(methodName)) {
assertEquals(2, countEmpRows());
}
}

private void executeSqlScript(String sqlResourcePath)
throws DataAccessException {
Resource resource = this.applicationContext
.getResource(sqlResourcePath);
new ResourceDatabasePopulator(false, false, null, resource)
.execute(jdbcTemplate.getDataSource());
}

private int countEmpRows() {
List empNames = jdbcTemplate.queryForList(
"select name from employee", String.class);
System.out.println(empNames);
return empNames.size();
}

@Configuration
static class Config {

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()//
.setName("empty-sql-scripts-without-tx-mgr-test-db")//
.build();
}

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

@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
}

Output:

Mar 14, 2016 7:52:56 AM org.springframework.test.context.support.AbstractContextLoader generateDefaultLocations
INFO: Could not detect default resource locations for test class [com.javarticles.spring.SpringTransactionalAnnotationExample]: no resource found for suffixes {-context.xml}.
Mar 14, 2016 7:52:56 AM org.springframework.test.context.support.AbstractDelegatingSmartContextLoader processContextConfiguration
INFO: AnnotationConfigContextLoader detected default configuration classes for context configuration [ContextConfigurationAttributes@1ff8b8f declaringClass = 'com.javarticles.spring.SpringTransactionalAnnotationExample', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', locations = '{}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass = 'org.springframework.test.context.ContextLoader'].
Mar 14, 2016 7:52:56 AM org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
INFO: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
Mar 14, 2016 7:52:56 AM org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
INFO: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
Mar 14, 2016 7:52:56 AM org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
INFO: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@7f63425a, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@36d64342, org.springframework.test.context.support.DirtiesContextTestExecutionListener@39ba5a14, org.springframework.test.context.transaction.TransactionalTestExecutionListener@511baa65, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@340f438e]
Mar 14, 2016 7:52:56 AM org.springframework.context.support.GenericApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericApplicationContext@737996a0: startup date [Mon Mar 14 07:52:56 IST 2016]; root of context hierarchy
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory initDatabase
INFO: Starting embedded database: url='jdbc:hsqldb:mem:empty-sql-scripts-without-tx-mgr-test-db', username='sa'
Before Transaction
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.init.ScriptUtils executeSqlScript
INFO: Executing SQL script from class path resource [com/javarticles/spring/schema.sql]
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.init.ScriptUtils executeSqlScript
INFO: Executed SQL script from class path resource [com/javarticles/spring/schema.sql] in 4 ms.
Mar 14, 2016 7:52:57 AM org.springframework.test.context.transaction.TransactionContext startTransaction
INFO: Began transaction (1) for test context [DefaultTestContext@3eb25e1a testClass = SpringTransactionalAnnotationExample, testInstance = com.javarticles.spring.SpringTransactionalAnnotationExample@477b4cdf, testMethod = rollbackDataByDefault@SpringTransactionalAnnotationExample, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@4d5d943d testClass = SpringTransactionalAnnotationExample, locations = '{}', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@77c2494c]; rollback [true]Test: rollbackDataByDefault
Is flagged for rollback? true
Mar 14, 2016 7:52:57 AM org.springframework.test.context.transaction.TransactionContext endTransaction
INFO: Rolled back transaction for test context [DefaultTestContext@3eb25e1a testClass = SpringTransactionalAnnotationExample, testInstance = com.javarticles.spring.SpringTransactionalAnnotationExample@477b4cdf, testMethod = rollbackDataByDefault@SpringTransactionalAnnotationExample, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@4d5d943d testClass = SpringTransactionalAnnotationExample, locations = '{}', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].
[admin]Before Transaction
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.init.ScriptUtils executeSqlScript
INFO: Executing SQL script from class path resource [com/javarticles/spring/schema.sql]
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.init.ScriptUtils executeSqlScript
INFO: Executed SQL script from class path resource [com/javarticles/spring/schema.sql] in 2 ms.
Mar 14, 2016 7:52:57 AM org.springframework.test.context.transaction.TransactionContext startTransaction
INFO: Began transaction (1) for test context [DefaultTestContext@3eb25e1a testClass = SpringTransactionalAnnotationExample, testInstance = com.javarticles.spring.SpringTransactionalAnnotationExample@510f3d34, testMethod = rollbackFalse@SpringTransactionalAnnotationExample, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@4d5d943d testClass = SpringTransactionalAnnotationExample, locations = '{}', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@77c2494c]; rollback [false]Test: rollbackFalse
Is flagged for rollback? false
Mar 14, 2016 7:52:57 AM org.springframework.test.context.transaction.TransactionContext endTransaction
INFO: Committed transaction for test context [DefaultTestContext@3eb25e1a testClass = SpringTransactionalAnnotationExample, testInstance = com.javarticles.spring.SpringTransactionalAnnotationExample@510f3d34, testMethod = rollbackFalse@SpringTransactionalAnnotationExample, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@4d5d943d testClass = SpringTransactionalAnnotationExample, locations = '{}', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].
[Joe, admin]
Before Transaction
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.init.ScriptUtils executeSqlScript
INFO: Executing SQL script from class path resource [com/javarticles/spring/schema.sql]
Mar 14, 2016 7:52:57 AM org.springframework.jdbc.datasource.init.ScriptUtils executeSqlScript
INFO: Executed SQL script from class path resource [com/javarticles/spring/schema.sql] in 0 ms.
Mar 14, 2016 7:52:57 AM org.springframework.test.context.transaction.TransactionContext startTransaction
INFO: Began transaction (1) for test context [DefaultTestContext@3eb25e1a testClass = SpringTransactionalAnnotationExample, testInstance = com.javarticles.spring.SpringTransactionalAnnotationExample@b2c9a9c, testMethod = commitData@SpringTransactionalAnnotationExample, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@4d5d943d testClass = SpringTransactionalAnnotationExample, locations = '{}', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@77c2494c]; rollback [true]Test: commitData
Is transaction active? true
Mar 14, 2016 7:52:57 AM org.springframework.test.context.transaction.TransactionContext endTransaction
INFO: Committed transaction for test context [DefaultTestContext@3eb25e1a testClass = SpringTransactionalAnnotationExample, testInstance = com.javarticles.spring.SpringTransactionalAnnotationExample@b2c9a9c, testMethod = commitData@SpringTransactionalAnnotationExample, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@4d5d943d testClass = SpringTransactionalAnnotationExample, locations = '{}', classes = '{class com.javarticles.spring.SpringTransactionalAnnotationExample$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].
[Joe, admin]

Download the source code

This was an example about spring @Transactional annotation.

You can download the source code here: springTransactionalAnnotationExamplezip

Share

Advertisement

Related

Advertisement

Latest

Advertisement