Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Transaction Management in Spring Framework

Tech 3

Understanding Transactions

A transaction is a logical unit of work that consists of a series of operations executed as a single unit. It ensures data consistency by transitioning the database from one consistent state to another. In simpler terms, if a set of processing steps either all succeed or all fail, data integrity is maintained.

JDBC Transaction Handling Logic

JDBC Transaction Flow

Transaction Properties (ACID)

Transactions have four essential properties known as ACID:

  • Atomicity: A transaction is an indivisible unit of work. All operations within a transaction must complete successfully, or none should take affect.
  • Consistency: A transaction must transition the database from one valid state to another, maintaining all defined rules and constraints.
  • Isolation: Concurrent transactions must not interfere with each other. Each transaction should operate independently.
  • Durability: Once a transaction is committed, its changes become permanent and survive subsequent system failures.

Transaction Propagation Bheavior

Propagation behavior defines how transactions behave when methods call each other across different transactional contexts. Common propagation types include:

  • PROPAGATION_REQUIRED: Uses the current transaction if one exists; otheriwse, creates a new one.
  • PROPAGATION_REQUIRES_NEW: Always creates a new transaction, suspending the current one if it exists.

Transaction Management Types

Programmatic Transaction Management

  • Provides explicit control over transaction boundaries in code.
  • Enables fine-grained transaction control.
  • Couples transaction logic with business code, reducing reusability and maintainability.

Declarative Transaction Management

  • Leverages Spring AOP to apply transactions through configuration.
  • Decouples transaction management from bussiness logic.
  • Easeir to maintain and modify.
  • Generally preferred in practical development.

Spring's Role in Transaction Management

  1. While transactions originate at the database/DAO layer, they're typically elevated to the service layer for better business logic management.
  2. Spring doesn't manage transactions directly but provides transaction managers for different persistence technologies:
    • DataSourceTransactionManager for JDBC-based frameworks (including MyBatis)
    • HibernateTransactionManager for Hibernate

All transaction managers implement the PlatformTransactionManager interface, which provides methods for transaction management.

Spring Transaction Implementation Approaches

  • XML-based configuration
  • Annotation-based configuration
  • AspectJ-based configuration

Practical Example: Fund Transfer Simulation

This example demonstrates a fund transfer scenario between two bank accounts.

Database Schema

Database Schema Sample Data

Data Access Layer

Interface:

package com.example.bank.dao;

public interface AccountRepository {
    boolean deposit(String accountNumber, double amount);
    boolean withdraw(String accountNumber, double amount);
}

Implementation:

package com.example.bank.dao.impl;

import org.springframework.jdbc.core.JdbcTemplate;
import com.example.bank.dao.AccountRepository;

public class AccountRepositoryImpl implements AccountRepository {
    
    private JdbcTemplate jdbcTemplate;
    
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public boolean deposit(String accountNumber, double amount) {
        String sql = "UPDATE ACCOUNTS SET BALANCE = BALANCE + ? WHERE ACCOUNT_NUMBER = ?";
        int rowsUpdated = jdbcTemplate.update(sql, amount, accountNumber);
        return rowsUpdated > 0;
    }
    
    @Override
    public boolean withdraw(String accountNumber, double amount) {
        String sql = "UPDATE ACCOUNTS SET BALANCE = BALANCE - ? WHERE ACCOUNT_NUMBER = ?";
        int rowsUpdated = jdbcTemplate.update(sql, amount, accountNumber);
        return rowsUpdated > 0;
    }
}

Service Layer

Interface:

package com.example.bank.service;

public interface TransferService {
    boolean transferFunds(String sourceAccount, String destinationAccount, double amount);
}

Implementation:

package com.example.bank.service.impl;

import com.example.bank.dao.AccountRepository;
import com.example.bank.service.TransferService;

public class TransferServiceImpl implements TransferService {
    
    private AccountRepository accountRepository;
    
    public void setAccountRepository(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }
    
    @Override
    public boolean transferFunds(String sourceAccount, String destinationAccount, double amount) {
        System.out.println("Starting fund transfer...");
        
        // Withdraw from source account
        boolean withdrawalSuccess = accountRepository.withdraw(sourceAccount, amount);
        System.out.println("Transfer amount: " + amount);
        
        // Simulate system failure
        int error = 1 / 0;
        
        // Deposit to destination account
        boolean depositSuccess = accountRepository.deposit(destinationAccount, amount);
        
        boolean transferSuccess = withdrawalSuccess && depositSuccess;
        System.out.println("Fund transfer completed.");
        
        return transferSuccess;
    }
}

1. XML-Based Transaction Configuration

Configuration File:

<?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: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/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="transferService" class="com.example.bank.service.impl.TransferServiceImpl">
        <property name="accountRepository" ref="accountRepository"/>
    </bean>

    <bean id="accountRepository" class="com.example.bank.dao.impl.AccountRepositoryImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <context:property-placeholder location="classpath:database.properties"/>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${db.driver}"/>
        <property name="jdbcUrl" value="${db.url}"/>
        <property name="user" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>

    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionProxy" 
          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="target" ref="transferService"/>
        <property name="transactionAttributes">
            <props>
                <prop key="transfer*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>

Transaction Proxy Configuration

Test Class:

package com.example.bank.test;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.bank.service.TransferService;

public class XmlTransactionTest {
    
    private ApplicationContext context;
    
    @Before
    public void setup() {
        context = new ClassPathXmlApplicationContext("xml-transaction-config.xml");
    }
    
    @Test
    public void testFundTransfer() {
        // Note: Using proxy bean ID
        TransferService transferService = 
            (TransferService) context.getBean("transactionProxy");
        
        transferService.transferFunds("ACC001", "ACC002", 100.0);
    }
}

Result: The transaction rolls back when the simulated error occurs, leaving account balances unchanged.

2. Annotation-Based Transaction Configuration

Updated Configuration:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- Existing bean definitions -->
    
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Enable annotation-driven transaction management -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

Annotated Service Implementation:

package com.example.bank.service.impl;

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.example.bank.dao.AccountRepository;
import com.example.bank.service.TransferService;

public class TransferServiceImpl implements TransferService {
    
    private AccountRepository accountRepository;
    
    public void setAccountRepository(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }
    
    @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
    @Override
    public boolean transferFunds(String sourceAccount, String destinationAccount, double amount) {
        System.out.println("Starting fund transfer...");
        
        boolean withdrawalSuccess = accountRepository.withdraw(sourceAccount, amount);
        System.out.println("Transfer amount: " + amount);
        
        // Simulate system failure
        int error = 1 / 0;
        
        boolean depositSuccess = accountRepository.deposit(destinationAccount, amount);
        
        boolean transferSuccess = withdrawalSuccess && depositSuccess;
        System.out.println("Fund transfer completed.");
        
        return transferSuccess;
    }
}

Test Class:

package com.example.bank.test;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.bank.service.TransferService;

public class AnnotationTransactionTest {
    
    private ApplicationContext context;
    
    @Before
    public void setup() {
        context = new ClassPathXmlApplicationContext("annotation-transaction-config.xml");
    }
    
    @Test
    public void testFundTransfer() {
        // Using the actual service bean ID
        TransferService transferService = 
            (TransferService) context.getBean("transferService");
        
        transferService.transferFunds("ACC001", "ACC002", 100.0);
    }
}

3. AspectJ-Based Transaction Configuration

Configuration File:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- Existing bean definitions -->
    
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Transaction advice configuration -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transfer*" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- AOP configuration -->
    <aop:config>
        <aop:pointcut id="serviceMethods" 
                      expression="execution(* com.example.bank.service.*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="serviceMethods"/>
    </aop:config>
</beans>

Test Class:

package com.example.bank.test;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.bank.service.TransferService;

public class AspectJTransactionTest {
    
    private ApplicationContext context;
    
    @Before
    public void setup() {
        context = new ClassPathXmlApplicationContext("aspectj-transaction-config.xml");
    }
    
    @Test
    public void testFundTransfer() {
        TransferService transferService = 
            (TransferService) context.getBean("transferService");
        
        transferService.transferFunds("ACC001", "ACC002", 100.0);
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.