스프링 프레임워크는 프로그래밍 방식 및 선언식 트랜잭션 관리를 모두 지원한다. 프로그래밍 방식 트랜잭션 관리의 경우 스프링의 트랜잭션 관리 추상화를 사용해 명시적으로 트랜잭션을 시작, 종료, 커밋한다. 선언식 트랜잭션 관리의 경우 단일 트랜잭션 내에서 실행할 메서드에 스프링의 @Transactional 어노테이션을 지정한다.
먼저 7-2절에서 설명된 MyBank 애플리케이션의 트랜잭션 관리 요구사항에 대해 알아보자.
MyBank의 트랜잭션 관리 요구사항
7-2절에서는 은행 고객이 신규 정기 예금 계좌를 개설하면 BANK_ACCOUNT_DETAILS 테이블의 BALANCE_AMOUNT 열에서 예금이 인출되고 FIXED_DEPOSIT_DETAILS 테이블에 정기 예금 내역이 저장된다고 설명했다.
다음 순서도에는 FixedDepositServiceImpl 클래스의 createFixedDeposit 메서드가 FIXED_DEPOSIT_DETAILS 테이블에 정기 예금 내역을 저장하고 BANK_ACCOUNT_DETAILS 테이블의 이와 연결된 은행 계좌에서 정기 예금 금액을 인출하는 과정이 나온다.

이 순서도를 보면 FixedDepositServiceImpl의 createFixedDeposit 메서드가 FixedDepositDaoImpl의 createFixedDeposit 메서드와 BankAccountDaoImpl의 subtractFromAccount 메서드를 호출하는 것을 알 수 있다. FixedDepositDaoImpl의 createFixedDeposit 메서드는 FIXED_DEPOSIT_DETAILS 테이블에 정기 예금 내역을 저장한다. BankAccountDaoImpl의 subtractFromAccount 메서드는 먼저 고객의 은행 계좌에 정기 예금 금액만큼 잔고가 있는지 확인하고 잔고가 있는 경우 계좌에서 개설하려는 정기 예금 금액을 인출한다. 잔고가 부족한 경우 BankAccountDaoImpl의 subtractFromAccount 메서드는 예외를 생성한다. FixedDepositDaoImpl의 createFixedDeposit 메서드 또는 BankAccountDaoImpl의 subtractFromAccount 메서드에서 어떤 이유에서든 오류가 발생하는 경우 시스템이 일관되지 않은 상태가 되므로 두 메서드는 단일 트랜잭션 내에서 실행돼야 한다.
다음으로 스프링을 사용해 프로그래밍 방식으로 MyBank 애플리케이션의 트랜잭션을 관리하는 방법을 알아보자.
프로그래밍 방식의 트랜잭션 관리
프로그래밍 방식으로 트랜잭션을 관리하려면 스프링의 TransactionTemplate 클래스를 사용하거나 스프링 PlatformTransactionManager 인터페이스의 구현을 사용하면 된다. TransactionTemplate 클래스는 트랜잭션의 시작 및 커밋을 담당해서 트랜잭션 관리를 간소화해준다. 개발자는 단일 트랜잭션 내에서 실행될 코드를 포함하는 스프링 TransactionCallback 인터페이스의 구현을 제공하면 된다.
예제 프로젝트: chapter 7/ch07-bankapp-tx-jdbc(이 프로젝트의 MyBank 애플리케이션에서는 스프링의 TransactionTemplate 클래스를 사용해 프로그래밍 방식으로 트랜잭션을 관리하는 방법을 보여준다. 애플리케이션을 실 행하려면 이 프로젝트에서 BankApp 클래스의 main 메서드를 실행한다. 또한 ch07-bankapp-jdbc 프로젝트와 마찬가지로 SPRING_BANK_APP_DB 데이터베이스와 BANK_ACCOUNT_DETAILS 및 FIXED_DEPOSIT_DETAILS 테이블을 만들어야 한다.)
다음 애플리케이션 컨텍스트 XML 파일에는 TransactionTemplate 클래스를 구성하는 방법이 나온다.
예제 7-12 applicationContext.xml - TransactionTemplate 구성
- 프로젝트: ch07-bankapp-tx-jdbc
- 소스 위치: src/main/resources/META-INF/spring
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource".....>
.....
</bean>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED" />
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED" />
</bean>
TransactionTemplate의 transactionManager 속성에는 트랜잭션을 관리하는 스프링의 PlatformTransactionManager 구현을 지정한다.
TransactionTemplate의 isolationLevelName 속성은 트랜잭션 관리자가 관리하는 트랜잭션의 트랜잭션 격리 수준을 스프링 TransactionDefinition 인터페이스에 정의된 상수로 지정한다. 예를 들어, ISOLATION_READ_UNCOMMITTED는 TransactionDefinition 인터페이스에 정의된 상수이며 커밋되지 않은 트랜잭션의 변경 내용을 다른 트랜잭션이 읽을 수 있음을 가리킨다.
TransactionTemplate의 propagationBehaviorName 속성에는 트랜잭션 전파 동작을 지정하며, 스프링 TransactionDefinition 인터페이스에 정의된 상수로 지정할 수 있다. 예를 들어, TransactionDefinition 인터페이스의 상수인 PROPAGATION_REQUIRED는 다음과 같은 의미다.
- 메서드를 한 트랜잭션 내에서 호출하지 않은 경우 트랜잭션 관리자가 새 트랜잭션을 시작하고 새 트랜잭션 내에서 메서드를 실행한다.
- 메서드를 한 트랜잭션 내에서 호출한 경우 트랜잭션 관리자가 동일한 트랜잭션 내에서 메서드를 실행한다.
스프링은 애플리케이션에서 사용하는 데이터 접근 기술에 따라 선택 가능한 몇 가지 PlatformTransactionManager 구현을 제공한다. 예를 들어, 데이터베이스 상호작용에 JDBC를 사용하는 애플리케이션에는 DataSourceTransactionManager가 적합하며, 하이버네이트를 사용하는 경우 HibernateTransactionManager, 그리고 JPA의 EntityManager를 사용하는 경우 JpaTransactionManager가 적합하다. ch07-bankapp-tx-jdbc 프로젝트에서는 JDBC를 데이터 접근에 사용하므로 예제 7-12에서 TransactionTemplate의 transactionManager 속성에는 DataSourceTransactionManager 인스턴스를 지정하고, DataSourceTransactionManager의 dataSource 속성에는 DataSourceTransactionManager 인스턴스가 트랜잭션을 관리하는 데이터베이스를 나타내는 javax.sql.DataSource 객체를 지정한다.
다음 예제의 FixedDepositServiceImpl 클래스는 트랜잭션 관리를 위해 TransactionTemplate 인스턴스를 사용하는 방법을 보여준다.
예제 7-13: TransactionTemplate을 사용하는 FixedDepositServiceImpl 클래스
- 프로젝트: ch07-bankapp-tx-jdbc
- 소스 위치: src/main/java/sample/spring/chapter07/bankapp/service
package sample.spring.chapter07.bankapp.service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
.....
@Service(value = "FixedDepositService")
public class FixedDepositServiceImpl implements FixedDepositService {
@Autowired
private TransactionTemplate transactionTemplate;
.....
@Override
public int createFixedDeposit(final FixedDepositDetails fixedDepositDetails) throws Exception {
transactionTemplate.execute(new TransactionCallback<FixedDepositDetails>() {
public FixedDepositDetails doInTransaction(TransactionStatus status) {
try {
myFixedDepositDao.createFixedDeposit(fixedDepositDetails);
bankAccountDao.subtractFromAccount(
fixedDepositDetails.getBankAccountId(),
fixedDepositDetails.getFixedDepositAmount()
);
} catch (Exception e) { status.setRollbackOnly(); }
return fixedDepositDetails;
}
});
return fixedDepositDetails.getFixedDepositId();
}
.....
}
이 예제에서 FixedDepositServiceImpl의 createFixedDeposit 메서드(그림 7-2 참조)는 정기 예금 내역을 FIXED_DEPOSIT_DETAILS 테이블에 저장하고 BANK_ACCOUNT_DETAILS 테이블에 들어 있는 해당 은행 계좌에서 정금 예금 금액을 인출한다.
단일 트랜잭션 내에서 실행할 작업을 정의하는 TransactionCallback 인터페이스의 구현을 작성하면 TransactionTemplate의 execute 메서드가 TransactionCallback 인스턴스에 포함된 작업을 단일 트랜잭션 내에서 실행한다. TransactionCallback 인터페이스에 정의된 doInTransaction 메서드를 구현해 단일 트랜잭션 내에서 실행할 작업을 지정하면 TransactionTemplate의 execute 메서드가 단일 트랜잭션 내에서 TransactionCallback의 doInTransaction 메서드를 호출한다. doInTransaction 메서드는 트랜잭션의 결과를 제어하는 데 사용할 수 있는 TransactionStatus 객체를 받는다. 예제 7-13의 경우 TransactionCallback의 doInTransaction 메서드는 단일 트랜잭션 내에서 실행해야 하는 FixedDepositDaoImpl의 createFixedDeposit 메서드와 BankAccountDaoImpl의 subtractFromAccount 메서드를 호출한다. 두 메서드 중 하나라도 실패할 경우 트랜잭션을 롤백하기를 원하므로 예외가 발생하는 경우 TransactionStatus의 setRollbackOnly 메서드를 실행한다. TransactionStatus의 setRollbackOnly 메서드를 실행하면 TransactionTemplate 인스턴스가 트랜잭션을 롤백한다. doInTransaction 메서드에 포함된 작업이 java.lang.RuntimeException을 유발하는 경우 트랜잭션은 자동으로 롤백된다.
TransactionCallback 인스턴스는 doInTransaction 메서드가 반환하는 객체 형식을 참조하는 제네릭 형식의 인자를 받는다. 예제 7-13에서 doInTransaction 메서드는 FixedDepositDetails 객체를 반환한다. doInTransaction 메서드가 객체를 반환하지 않게 하려면 TransactionCallback 인터페이스를 구현하는 TransactionCallbackWithoutResult 추상 클래스를 구현하면 된다. TransactionCallbackWithoutResult 클래스는 doInTransaction 메서드가 값을 반환하지 않는 TransactionCallback 구현을 작성할 수 있게 허용한다.
다음 예제에서 BankApp 클래스의 main 메서드는 BankAccountServiceImpl의 createBankAccount 메서드를 호출해 은행 계좌를 생성하고 FixedDepositServiceImpl의 createFixedDeposit 메서드를 호출해 새로 생성한 은행 계좌와 연결된 정기 예금을 생성한다.
예제 7-14: BankApp 클래스
- 프로젝트: ch07-bankapp-tx-jdbc
- 소스 위치: src/main/java/sample/spring/chapter07/bankapp
package sample.spring.chapter07.bankapp;
public class BankApp {
.....
public static void main(String args[]) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:META-INF/spring/applicationContext.xml");
BankAccountService bankAccountService = context.getBean(BankAccountService.class);
FixedDepositService fixedDepositService = context.getBean(FixedDepositService.class);
BankAccountDetails bankAccountDetails = new BankAccountDetails();
bankAccountDetails.setBalanceAmount(1000);
.....
int bankAccountId = bankAccountService.createBankAccount(bankAccountDetails);
FixedDepositDetails fixedDepositDetails = new FixedDepositDetails();
fixedDepositDetails.setFixedDepositAmount(1500);
fixedDepositDetails.setBankAccountId(bankAccountId);
.....
int FixedDepositId = fixedDepositService.createFixedDeposit(fixedDepositDetails);
.....
}
}
이 예제에서는 먼저 잔고가 1000인 은행 계좌를 생성한 다음, 금액이 1500인 정기 예금을 생성했다. 이 경우 정기 예금 금액이 은행 계좌의 잔고보다 크므로 BankAccountDaoImpl의 subtractFromAccount 메서드에서 예외가 발생한다(그림 7-2에서 BankAccountDaoImpl의 subtractFromAccount 메서드 참조).
BankApp의 main 메서드를 실행해보면 FIXED_DEPOSIT_DETAILS 테이블에 정기 예금이 생성되지 않으며, BANK_ACCOUNT_DETAILS 테이블에서도 금액 1500이 인출되지 않는다. 즉, FixedDepositDaoImpl의 createFixedDeposit와 BankAccountDaoImpl의 subtractFromAccount가 동일한 트랜잭션 내에서 실행됐음을 알 수 있다.
TransactionTemplate 클래스 대신 PlatformTransactionManager 구현을 직접 사용해 프로그래밍 방식으로 트랜잭션을 관리하는 것도 가능하다. PlatformTransactionManager 구현을 사용하는 경우에는 트랜잭션을 명시적으로 시작 및 커밋(또는 롤백)해야 한다. 이를 감안하면 PlatformTransactionManager 구현을 직접 사용하기보다는 TransactionTemplate을 사용하는 것이 좋다.
다음으로 스프링의 선언식 트랜잭션 관리 기능에 대해 알아보자.
선언식 트랜잭션 관리
프로그래밍 방식의 트랜잭션 관리를 이용하면 애플리케이션 코드가 스프링 전용 클래스와 결합되는 부작용이 있다. 이와 달리 선언식 트랜잭션 관리를 사용할 때는 메서드나 클래스에 스프링의 @Transactional 어노테이션만 지정하면 된다. 특정 메서드에 @Transactional 어노테이션을 지정하면 해당 메서드가 단일 트랜잭션 내에서 실행되며, 특정 클래스의 모든 메서드를 단일 트랜잭션 내에서 실행하려면 해당 클래스에 @Transactional 어노테이션을 지정하면 된다.
선언식 트랜잭션 관리를 수행하는 방법에는 @Transactional 어노테이션을 사용하는 방법 외에도 스프링 tx 스키마 요소를 사용해 트랜잭션 메서드를 지정하는 방법도 있다. 그러나 스프링 tx 스키마를 사용하면 애플리케이션 컨텍스트 XML 파일이 복잡해지는 문제가 있으므로 여기서는 @Transactional 어노테이션을 사용하는 방법만 살펴보겠다.
예제 프로젝트: chapter 7/ch07-bankapp-jdbc 및 chapter 7/ch07-bankapp-hibernate(ch07-bankapp-jdbc 프로젝트의 MyBank 애플리케이션에서는 스프링 JDBC 모듈을 데이터베이스 상호작용에 사용한다(ch07-bankapp-jdbc 프로젝트에 대해서는 7-3절 참조). ch07-bankapp-hibernate 프로젝트의 MyBank 애플리케이션에서는 하이버네이트를 데이터베이스 상호작용에 사용한다(ch07-bankapp-hibernate 프로젝트에 대해서는 7-4절 참조).
선언식 트랜잭션 관리를 활성화하려면 스프링 tx 스키마의 <annotation-driven>
요소를 사용해야 한다. 다음 예제는 ch07-bankapp-jdbc 프로젝트에서 <annotation-driven>
요소를 사용하는 방법을 보여준다.
예제 7-15: applicationContext.xml - <annotation-driven>
요소
- 프로젝트: ch07-bankapp-jdbc
- 소스 위치: src/main/resources/META-INF/spring
<beans ..... xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=".....http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
.....
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
.....
</beans>
이 예제에서는 애플리케이션 컨텍스트 XML 파일에서 스프링 tx 스키마의 요소를 사용할 수 있게 먼저 스프링 tx 스키마를 포함했다. <annotation-driven>
요소는 선언식 트랜잭션 관리를 활성화한다. <annotation-driven>
요소의 transaction-manager 특성은 트랜잭션 관리에 사용할 PlatformTransactionManager 구현에 대한 참조를 지정하며, 이 예제에서는 ch07-bankapp-jdbc 프로젝트에 사용할 트랜잭션 관리자로 DataSourceTransactionManager를 지정했다.
다음 예제에서는 데이터 접근에 하이버네이트 ORM을 사용하는 ch07-bankapp-hibernate 프로젝트에서 선언식 트랜잭션 관리를 적용하는 방법을 보여준다.
예제 7-16: applicationContext.xml - <annotation-driven>
요소
- 프로젝트: ch07-bankapp-hibernate
- 소스 위치: src/main/resources/META-INF/spring
<beans ..... xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=".....http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
.....
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
.....
</beans>
이 예제를 예제 7-15와 비교하면 <annotation-driven>
요소의 transaction-manager 특성이 참조하는 PlatformTransactionManager 구현이 유일한 차이점이라는 것을 알 수 있다. 하이버네이트 ORM을 데이터베이스 상호작용에 사용하는 경우 이 예제에 나오는 것처럼 PlatformTransactionManager의 org.springframework.orm.hibernate4.HibernateTrans actionManager 구현을 트랜잭션 관리에 사용한다.
하이버네이트 3을 사용하는 경우 transaction-manager 특성에 org.springframework.orm.hibernate4.HibernateTransactionManager 대신 org.springframework.orm.hibernate3.HibernateTransactionManager로 설정한다.
다음 예제의 FixedDepositServiceImpl 클래스에서는 선언식 트랜잭션 관리를 사용하는 방법을 보여준다.
예제 7-17: FixedDepositServiceImpl 클래스 - @Transactional 어노테이션 사용
- 프로젝트: ch07-bankapp-jdbc
- 소스 위치: src/main/java/sample/spring/chapter07/bankapp/service
package sample.spring.chapter07.bankapp.service;
@Service(value = "FixedDepositService")
public class FixedDepositServiceImpl implements FixedDepositService {
.....
@Transactional
public int createFixedDeposit(FixedDepositDetails fixedDepositDetails) throws Exception {
bankAccountDao.subtractFromAccount(fixedDepositDetails.getBankAccountId(),
fixedDepositDetails.getFixedDepositAmount());
return myFixedDepositDao.createFixedDeposit(fixedDepositDetails);
}
.....
}
이 예제에서는 createFixedDeposit 메서드에 @Transactional 어노테이션을 지정해 이 메서드를 단일 트랜잭션 내에서 실행하게 했다. 트랜잭션을 관리하는 데는 <annotation-driven>
요소(예제 7-15 및 7-16 참조)의 transaction-manager 특성으로 지정한 트랜잭션 관리자가 사용된다. createFixedDeposit 메서드 실행 중 java.lang.RuntimeException이 발생하면 트랜잭션이 롤백된다.
@Transactional 어노테이션은 트랜잭션 관리자의 동작을 구성할 수 있는 특성을 정의한다. 예를 들어, rollbackFor 특성은 트랜잭션 롤백을 유발하는 예외 클래스를 지정한다. rollbackFor 특성으로 지정하는 예외 클래스는 java.lang.Throwable 클래스의 하위 클래스여야 한다. 비슷하게 isolation 특성은 트랜잭션 격리 수준을 지정한다.
애플리케이션이 여러 트랜잭션 관리자를 정의하는 경우 @Transactional 어노테이션의 value 특성으로 트랜잭션 관리에 사용할 PlatformTransactionManager 구현의 빈 이름을 지정할 수 있다. 다음 예제에서는 애플리케이션 컨텍스트 XML 파일에서 tx1 및 tx2라는 트랜잭션 관리자 두 개를 정의한다.
그중 tx1 트랜잭션 관리자는 SomeServiceImpl의 methodA에서 사용되고 tx2 트랜잭션 관리자는 SomeServiceImpl의 methodB에서 사용된다.
예제 7-18: @Transactional의 value 특성
----------------------- SomeServiceImpl 클래스 ------------------------
@Service
import org.springframework.transaction.annotation.Transactional;
.....
public class SomeServiceImpl implements SomeService {
.....
@Transactional(value = "tx1")
public int methodA() {.....}
@Transactional(value = "tx2")
public int methodB() {.....}
}
----------------------- 애플리케이션 컨텍스트 XML 파일 ------------------------
<tx:annotation-driven />
<bean id="tx1"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory1" ref="sessionFactory1"/>
</bean>
<bean id="tx2"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
이 예제에서는 @Transactional 어노테이션 자체에서 트랜잭션을 관리하는 데 사용할 트랜잭션 관리자를 지정했기 때문에 스프링 tx 스키마의 <annotation-driven>
요소에서 transaction-manager 특성을 지정하지 않았다. 이 예제에서는 @Transactional 어노테이션의 value 특성으로 트랜잭션 관리자를 지정했다. 즉, SomeServiceImpl의 methodA는 tx1 트랜잭션 관리자의 관리하에 실행되며 SomeServiceImpl의 methodB는 tx2 트랜잭션 관리자의 관리하에 실행된다.
다음으로 스프링의 JTA(Java Transaction API) 트랜잭션 지원에 대해 알아보자.
스프링의 JTA 지원
1장에서 단일 트랜잭션에 여러 트랜잭션 리소스가 관련된 경우 JTA를 사용해서 트랜잭션을 관리한다고 설명했다. 스프링은 애플리케이션에서 JTA 트랜잭션을 관리하는 데 사용할 수 있는 제네릭 JtaTransactionManager 클래스(PlatformTransactionManager 구현)를 제공한다.

대부분의 애플리케이션 서버 환경에서는 JtaTransactionManager로도 충분하지만, 스프링은 애플리케이션 서버 전용 기능을 사용해 JTA 트랜잭션을 관리할 수 있는 공급업체별 PlatformTransactionManager 구현도 제공한다. 스프링이 지원하는 공급업체별 JTA 트랜잭션 관리자에는 OC4JJtaTransactionManager (오라클 OC4J용), WebLogicJtaTransactionManager (웹로직 애플리케이션 서버용 ), 그리고 WebSphereUowTransactionManager (웹스피어 애플리케이션 서버용)가 있다. 그림 7-3은 JTA 트랜잭션 관리자와 리소스별 트랜잭션 관리자가 PlatformTransactionManager 인터페이스와 어떻게 연관되는지 보여준다. 이 그림을 보면 JTA 트랜잭션 관리자 클래스와 리소스별 트랜잭션 관리자 클래스에서 모두 PlatformTransactionManager를 구현하고 있음을 알 수 있다.
다음으로 애플리케이션 컨텍스트 XML 파일에서 JTA 트랜잭션 관리자를 간편하게 구성하는 방법을 알아보자.
<jta-transaction-manager>
요소로 JTA 트랜잭션 관리자 구성
스프링의 tx 스키마는 애플리케이션이 배포된 애플리케이션 서버를 자동으로 감지하고 적절한 JTA 트랜잭션 관리자를 구성하는 <jta-transaction-manager>
요소를 제공한다. 이 요소를 사용하면 애플리케이션 컨텍스트 XML 파일에서 직접 애플리케이션 서버별 JTA 트랜잭션 관리자를 구성하는 수고를 덜 수 있다. 예를 들어 애플리케이션을 웹스피어 애플리케이션 서버로 배포하는 경우 <jta-transactionmanager>
요소는 WebSphereUowTransactionManager의 인스턴스를 구성하며, 동일한 애플리케이션을 웹로직 애플리케이션 서버로 배포하는 경우 WebLogicJtaTransactionManager의 인스턴스를 구성한다. 그리고 애플리케이션이 OC4J, 웹스피어 또는 웹로직 외의 다른 애플리케이션 서버로 배포된 경우 <jta-transaction-manager>
요소는 JtaTransactionManager의 인스턴스를 구성한다.