Spring @Transactional Annotation Example

0

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.

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 [[email protected] 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[email protected]7f63425a, org.springframewor[email protected]36d64342, org.springfra[email protected]39ba5a14, org.springframew[email protected]511baa65, org.sp[email protected]340f438e]
Mar 14, 2016 7:52:56 AM org.springframework.context.support.GenericApplicationContext prepareRefresh
INFO: Refreshing [email protected]7996a0: 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 [[email protected] testClass = SpringTransactionalAnnotationExample, testInstance = [email protected]4cdf, testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] 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 [o[email protected]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 [[email protected] testClass = SpringTransactionalAnnotationExample, testInstance = [email protected]4cdf, testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] 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 [[email protected] testClass = SpringTransactionalAnnotationExample, testInstance = [email protected]3d34, testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] 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 [o[email protected]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 [[email protected] testClass = SpringTransactionalAnnotationExample, testInstance = [email protected]3d34, testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] 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 [[email protected] testClass = SpringTransactionalAnnotationExample, testInstance = [email protected]a9c, testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] 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 [o[email protected]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 [[email protected] testClass = SpringTransactionalAnnotationExample, testInstance = [email protected]a9c, testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] 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

About Author

Ram's expertise lies in test driven development and re-factoring. He is passionate about open source technologies and loves blogging on various java and open-source technologies like spring. You can reach him at [email protected]

Comments are closed.