Spring 트랜잭션 기초와 @Transactional 활용

Spring에서 데이터베이스 트랜잭션을 처리하려면 aspectjrtaspectjweaver 의존성이 필요합니다.

필수 의존성

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.5</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

엔티티 및 서비스 계층

은행 계좌 이체를 담당하는 서비스를 구현합니다. @Transactional을 적용하여 예외 발생 시 자동 롤백되도록 설정합니다.

package com.example.bank;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransferService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void transferFunds() {
        jdbcTemplate.update(
            "UPDATE account SET balance = balance - ? WHERE holder = ?",
            500, "zhangsan"
        );
        
        // 의도적으로 예외를 발생시켜 롤백 여부를 확인
        int error = 1 / 0;
        
        jdbcTemplate.update(
            "UPDATE account SET balance = balance + ? WHERE holder = ?",
            500, "lisi"
        );
    }
}

설정 클래스

Java 기반 설정으로 DataSource와 JdbcTemplate을 구성합니다.

package com.example.bank;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;

@Configuration
@ComponentScan(basePackages = "com.example.bank")
public class DatabaseConfig {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/bankdb?serverTimezone=Asia/Seoul");
        ds.setUsername("root");
        ds.setPassword("secret");
        return ds;
    }

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

XML 기반 트랜잭션 설정

선언적 트랜잭션을 XML로 구성하는 방법입니다. 트랜잭션 매니저 등록 후 <tx:annotation-driven/>으로 애노테이션 기반 트랜잭션을 활성화합니다.

<?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:component-scan base-package="com.example.bank"/>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/bankdb?serverTimezone=Asia/Seoul"/>
        <property name="username" value="root"/>
        <property name="password" value="secret"/>
    </bean>

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

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

    <tx:annotation-driven transaction-manager="txManager"/>

</beans>

테스트 검증

JUnit으로 트랜잭션 롤백이 정상 작동하는지 확인합니다. transferFunds() 실행 중 ArithmeticException이 발생하면 이전 UPDATE문이 롤백되어 잔액이 변경되지 않아야 합니다.

package com.example.bank;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransferTest {

    private TransferService transferService;

    @Before
    public void setup() {
        ClassPathXmlApplicationContext ctx = 
            new ClassPathXmlApplicationContext("applicationContext.xml");
        transferService = ctx.getBean(TransferService.class);
    }

    @Test(expected = ArithmeticException.class)
    public void testRollbackOnError() {
        transferService.transferFunds();
        // 예외 발생 시 트랜잭션이 롤백되어 zhangsan의 잔액이 감소하지 않아야 함
    }
}

핵심 포인트

  • @Transactional 미적용 시: zhangsan 잔액만 500 감소되고 lisi 잔액은 불변 (데이터 불일치)
  • @Transactional 적용 시: 예외 발생으로 인해 전체 작업 롤백, 양쪽 잔액 모두 변경되지 않음
  • 트랜잭션 매니저는 DataSourceTransactionManager를 사용하며, PlatformTransactionManager 인터페이스를 구현

태그: Spring Framework Transaction Management JDBC JUnit aspectj

6월 21일 00:23에 게시됨