java-school logo

Spring 트랜잭션

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.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">
	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />
	
	<aop:aspectj-autoproxy />

	<bean id="testLogger" class="net.java_school.commons.TestLogger" />

	<bean id="bankUi" class="net.java_school.bank.BankUi">
		<property name="stream" value="#{T(System).out}" />
		<property name="bank" ref="shinhanBank" />
	</bean>

	<bean id="shinhanBank" class="net.java_school.bank.ShinhanBank">
		<property name="dao" ref="shinhanBankDao" />
	</bean>

	<bean id="shinhanBankDao" class="net.java_school.bank.ShinhanBankDao">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:XE" />
		<property name="username" value="scott" />
		<property name="password" value="tiger" />
	</bean>

</beans>

지금까지 입금과 출금 메서드는 void를 반환하도록 설계했기에 입금이나 출금이 성공 또는 실패했는지를 알 수 없었다. 입금과 출금시 dao가 정수값을 리턴하도록 자바 클래스를 수정한다.

BankDao.java
//입금
public int deposit(String accountNo, long amount);

//출금
public int withdraw(String accountNo, long amount);
ShinhanBankDao.java
@Override
public int deposit(String accountNo, long amount) {
	Map<String, Object> params = new HashMap<String, Object>();
	params.put("amount", amount);
	params.put("accountNo", accountNo);
	
	return getNamedParameterJdbcTemplate().update(DEPOSIT, params);
}

@Override
public int withdraw(String accountNo, long amount) {
	Map<String, Object> params = new HashMap<String, Object>();
	params.put("amount", amount);
	params.put("accountNo", accountNo);
	
	return getNamedParameterJdbcTemplate().update(WITHDRAW, params);		
}

다음과 같이 어노테이션을 사용해 트랜잭션을 설정한다.

ShinhanBank.java
package net.java_school.bank;

import java.util.List;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation=Propagation.SUPPORTS)
public class ShinhanBank implements Bank {
	
	//..중간 생략..
	
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	public void transfer(String from, String to, long amount) {
		int check = dao.withdraw(from, amount);
		if (check < 1) {
			throw new RuntimeException("출금 실패");
		}
		check = dao.deposit(to, amount);
		if (check < 1) {
			throw new RuntimeException("입금 실패");
		}
	}

	//..중간 생략..
	
}

테스트하기 전에 BankUi.java의 메인 메서드를 아래를 참조해 수정한다.

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //XML
//AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BankConfig.class); //JavaConfig

컴파일하고 실행한 후, 101 계좌에서 505 계좌(존재하는 않는 계좌)로 이체를 시도한다. 테스트에 성공했다면(즉, 이체가 취소되었다면), ShinhanBank 클래스에서 transfer() 메서드 위에 있는 @Transactional(propagation=Propagation.REQUIRED)을 제거하고 다시 이체를 시도한다.

JavaConfig 설정

BankConfig.java
package net.java_school.bank;

import javax.sql.DataSource;

import net.java_school.commons.TestLogger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class BankConfig {

	@Bean
	public TestLogger testLogger() {
		return new TestLogger();
	}

	@Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:XE");
        dataSource.setUsername("scott");
        dataSource.setPassword("tiger");
        return dataSource;
    }
	
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
    
    @Bean
    public BankDao shinhanBankDao() {
        ShinhanBankDao bankDao = new ShinhanBankDao();
        bankDao.setDataSource(dataSource());
        return bankDao;
    }

	@Bean
	public Bank shinhanBank() {
		Bank bank = new ShinhanBank();
		bank.setDao(shinhanBankDao());
		return bank;
	}

	@Bean
	public BankUi bankUi() {
		BankUi ui = new BankUi();
		ui.setBank(shinhanBank());
		ui.setStream(System.out);
		return ui;
	}
	
}

테스트하기 전에, applicationContext.xml 설정을 모두 주석 처리한다. BankUi.java의 메인 메서드를 아래를 참조해 수정한다.

//ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //XML
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BankConfig.class); //JavaConfig

최종 소스

예제 다운로드