spring

Transactions in Spring

In this article we will explain Transaction in Spring.

1 What is a transaction?

A transaction is a group of updates that are treated as an atomic unit: either all the updates must complete successfully, or none must complete.

A classic example illustrating the need for such a thing as a transaction is that of a transfer of funds between two accounts in a banking application. A transfer consists of a withdrawal of an amount of money from a source account and a deposit of that same amount to a destination account. Ideally, we’d want to avoid having one half of a transfer happening without the other half, lest there be chaos on the bank’s books. And it gets even more complicated: withdrawals and deposits are, themselves, composite operations involving several database operations apiece. Here’s one possible breakdown of the steps that might be involved in recording a transfer in a banking database:

  • Transfer
    • Withdrawal
      • Add an activity detail record showing the outgoing transfer from the source account
      • Update the balance of the source account record to reflect the withdrawn amount
    • Deposit
      • Add an activity detail record showing the incoming transfer to the destination account
      • Update the balance of the destination account to reflect the deposited amount

These steps have to be performed sequentially, which means there is an opportunity for some technical issue to interrupt the sequence at any point. Without transactions, an interruption means that some steps will be performed and some will not, leaving the database in an inconsistent state.

With transactions, however, we explicitly identify all steps that make up a transfer as a related group that must be processed as a unit. The database system will ensure that the entire transaction either succeed or fail as a whole. The system also ensures that other database users never the database in a state where a transaction has been partially completed. And if a technical failure leaves a database with one or more transactions in a state of “limbo”, the system can automatically sort things out and restore the database to a consistent state.

2. About The Example

The example code provided with this article gives a simple, if somewhat contrived, demonstration of the basics of transactional functionality with Spring. We start by populating an embedded H2 database with a few rows of data. We then make two passes over the data, each of which sets out to modify all the sample rows within a transaction. The first pass is rigged to simulate a database failure partway through the transaction while the second pass completes normally. Both before and after each pass, we display the database contents both before and after each pass.

The following technologies were used in this example:

  • Java SE 11
  • Spring Framework 5.2.13
  • Spring Data JPA 2.3.7
  • Hibernate 5.4.28
  • H2 1.4.200

3. Configure Transactions

Configuring a Spring application to use transactions requires three steps:

  • Add the @EnableTransactionManagement annotation on a @Configuration-annotated configuration class (and include that configuration class as a constructor argument when creating the application’s AnnotationConfigApplicationContext);
  • Add a @Bean method that returns an appropriate implementation of org.springframework.TransactionManager ;
  • Write @Transactional-annotated service methods that implement your transactions.

Here is the Spring Java-based configuration used in our example; the transaction-related items are highlighted:

AppConfig.java

@Configuration
@ComponentScan(basePackageClasses = {
    com.jcg.springtxndemo.repo.packageInfo.class,
    com.jcg.springtxndemo.app.packageInfo.class,
    com.jcg.springtxndemo.service.packageInfo.class})
@EnableJpaRepositories("com.jcg.springtxndemo.repo")
@EnableTransactionManagement
public class AppConfig
{
    // Has to be "entityManagerFactory" for Spring Data Jpa
    @Bean
    LocalContainerEntityManagerFactoryBean entityManagerFactory()
    {
        HibernateJpaVendorAdapter vendorAdapter =
            new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean cmf =
            new LocalContainerEntityManagerFactoryBean();
        cmf.setDataSource(dataSource());
        cmf.setJpaVendorAdapter(vendorAdapter);
        return cmf;
    }

    @Bean
    DataSource dataSource()
    {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder
            .setName("MyDb")
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }

    @Bean
    PlatformTransactionManager transactionManager(EntityManagerFactory emf)
    {
        return new JpaTransactionManager(emf);
    }

    @Bean(name = "conversionService")
    ConversionService conversionSvc()
    {
        return new DefaultConversionService();
    }
}

4. The @Transactional Annotation

To define a transaction, place the statements which make up the transaction in a single method, and annotate that method with @Transactional.

SpringTxtDemoSvcImpl.java

    @Override
    @Transactional
    public void demoUpdate(boolean succeed)
    {
        int count = 0;
        for (MyTable mt : ftRepo.findAll()) {
            String newName = mt.getName() + " one";
            LOG.info(String.format("Changing \"%s\" to \"%s\"", mt.getName(), newName));
             mt.setName(newName);
            ftRepo.save(mt);
            ++count;
            if (!succeed && count >= 2) {
                RuntimeException ex = new RuntimeException("OOPS! Something bad happened!");
                LOG.throwing(getClass().getName(), "demoUpdate", ex);
                throw ex;
            }
        }
    }

5. Transactions and Proxies in Spring

Spring implements the @Transactional annotation by using proxies for the affected classes. While you don’t necessarily need to understand the all details of this implementation in order to use transactions, you do need to be aware of certain interesting side effects of this design choice. The main one is that @Transactional annotation is ineffective when the @Transactional method is called from another method in the same class. To be safe, keep your @Transactional methods in service-layer classes, separate from the business-logic classes that call them.

The article Transactions, Caching and AOP: Understanding Proxy Usage in Spring explains more about how Spring uses proxies for transactions, and other functionality as well.

6. Read-Only Transactions

Transactions can also be helpful even when you are not making changes to data. Read-only transactions can eliminate overhead, such as locking, that would normally be associated with reading rows that you intended to update. Database systems will normally assume that any row you read during a “regular” read/write transaction should be locked, thereby ensuring that your application will be able to complete its update without interference from other database users. But interference from others is generally not an issue if you do not intend to update data immediately after reading it, so you can avoid the overhead of locking in such cases by using a read-only transaction.

To specify a read-only transaction, add the element readOnly=true to the @Transactional annotation:

SpringTxnDemoSvcImpl.java

    @Override
    @Transactional(readOnly = true)
    public void listDb(String header)
    {
        StringBuilder sb = new StringBuilder();
        sb.append(header).append('\n');
        for (MyTable mt : ftRepo.findAll()) {
            sb.append(mt.toString()).append('\n');
        }
        LOG.info(sb.toString());
    }

7. Spring Transaction Logging

In order to verify that your transactions are being executed, you can turn up the logging level of your Spring transaction manager. For our example, which uses a JpaTransactionManager and the JDK native java.util.logging framework, we added the following line into our logging.properties configuration file:

org.springframework.orm.jpa.JpaTransactionManager.level=FINE

Here is an example of some of the messages logged by the JpaTransactionManager:

Example log

Mar 11, 2021 9:42:51 PM org.springframework.transaction.support.AbstractPlatformTransactionManager getTransaction
FINE: Creating new transaction with name [com.jcg.springtxndemo.service.SpringTxnDemoSvcImpl.demoUpdate]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doBegin
FINE: Opened new EntityManager [SessionImpl(196668120)] for JPA transaction
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doBegin
FINE: Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@6e807e2]
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doGetTransaction
FINE: Found thread-bound EntityManager [SessionImpl(196668120)] for JPA transaction
Mar 11, 2021 9:42:51 PM org.springframework.transaction.support.AbstractPlatformTransactionManager handleExistingTransaction
FINE: Participating in existing transaction
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doGetTransaction
FINE: Found thread-bound EntityManager [SessionImpl(196668120)] for JPA transaction
Mar 11, 2021 9:42:51 PM org.springframework.transaction.support.AbstractPlatformTransactionManager handleExistingTransaction
FINE: Participating in existing transaction
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doGetTransaction
FINE: Found thread-bound EntityManager [SessionImpl(196668120)] for JPA transaction
Mar 11, 2021 9:42:51 PM org.springframework.transaction.support.AbstractPlatformTransactionManager handleExistingTransaction
FINE: Participating in existing transaction
Mar 11, 2021 9:42:51 PM org.springframework.transaction.support.AbstractPlatformTransactionManager processRollback
FINE: Initiating transaction rollback
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doRollback
FINE: Rolling back JPA transaction on EntityManager [SessionImpl(196668120)]
Mar 11, 2021 9:42:51 PM org.springframework.orm.jpa.JpaTransactionManager doCleanupAfterCompletion
FINE: Closing JPA EntityManager [SessionImpl(196668120)] after transaction
Mar 11, 2021 9:42:51 PM com.jcg.springtxndemo.module.TxnDemoImpl trial
FINE: Transaction failed

8. Transaction Rollback

Reversing or canceling the effect of a partially completed transaction is called rollback: we say a transaction is being rolled back, or we are rolling back a transaction.

Rollback may be performed automatically by the database system as part of its recovery from a failure. Applications can also roll back transactions they have started, either voluntarily (e.g., due to an abnormal condition detected by business logic) or involuntarily (e.g., the result of a database error).

Transaction rollback normally occurs when a @Transactional method throws an unchecked exception (a subclass of RuntimeException) or an Error. In our example, we intentionally throw a RuntimeException in order to trigger a rollback:

SpringTxnDemoSvcImpl.java

    @Override
    @Transactional
    public void demoUpdate(boolean succeed)
    {
        int count = 0;
        for (MyTable mt : ftRepo.findAll()) {
            String newName = mt.getName() + " one";
            LOG.info(String.format("Changing \"%s\" to \"%s\"", mt.getName(), newName));
             mt.setName(newName);
            ftRepo.save(mt);
            ++count;
            if (!succeed && count >= 2) {
                RuntimeException ex = new RuntimeException("OOPS! Something bad happened!");
                LOG.throwing(getClass().getName(), "demoUpdate", ex);
                throw ex;
            }
        }
    }

Additionally, any exception thrown by the save call on our repository would also cause a rollback.

9. Download the Source Code


Download


You can download the full source code of this example here:

Transactions in Spring

Kevin Anderson

Kevin has been tinkering with computers for longer than he cares to remember.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button