Chapter 9. 트랜잭션 관리

9.1. 소개

Spring 프레임워크을 사용하기 위한 가장 강제적인 이유중 하나는 트랜잭션 지원이다. Spring 프레임워크는 다음의 이득을 부여하는 트랜잭션 관리를 위한 일관적인 추상화를 제공한다.

  • JTA, JDBC, Hibernate, JPA, 그리고 JDO와 같은 서로 다른 트랜잭션 API간의 일관적인 프로그래밍 모델을 제공한다.

  • 선언적인 트랜잭션 관리를 제공한다.

  • JTA와 같은 많은 개별 트랜잭션 API보다 좀더 간단하고, 사용하기 좀더 쉬운 프로그램으로 처리하는 트랜잭션 관리를 위한 API를 제공한다.

  • Spring 프레임워크의 다양한 데이터 접근 추상화와 통합한다.

이 장은 많은 부분으로 나뉜다. 각각은 Spring 프레임워크의 트랜잭션 지원의 추가된 값이나 기술중 하나를 언급한다. 이 장은 트랜잭션 관리에 대한 가장 좋은 상황에 대한 몇가지를 모은다(예를 들어, 선언적이고 프로그램으로 처리하는 트랜잭션 관리간의 선택).

  • 첫번째 부분, 동기은 EJB CMT나 Hibernate와 같은 적절한 API를 통해 트랜잭션을 다루는 것과 대비되는 Spring 프레임워크의 트랜잭션 추상화를 사용하길 원하는 이유를 언급한다.

  • 두번째 부분, Key 추상화는 다양한 소스로부터 DataSource 인스턴스를 설정하고 얻는 방법만큼 Spring 프레임워크의 트랜잭션 지원내 핵심 클래스의 개요를 말한다.

  • 세번째 부분, 선언적인 트랜잭션 관리는 선언적인 트랜잭션 관리를 위한 Spring 프레임워크의 지원을 다룬다.

  • 네번째 부분, 프로그램으로 처리하는 트랜잭션 관리는 프로그램으로 처리(명시적으로 코딩하는)하는 트랜잭션 관리를 위한 Spring 프레임워크의 지원을 다룬다.

9.2. 동기

전통적으로, J2EE 개발자들은 트랜잭션 관리에 있어 두 가지 선택사항(전역 또는 로컬 트랜잭션)을 가진다. 전역 트랜잭션은 Java트랜잭션 API(JTA)를 사용하여 애플리케이션 서버에 의해 관리된다. 로컬 트랜잭션은, 예를 들어 JDBC 커넥션과 연관된 트랜잭션처럼, 자원 특성을 따른다. 이 선택은 심오한 의미를 가진다. 예를 들어, 전역 트랜잭션은 다중 트랜잭션 자원(대개 관계형 데이터베이스와 메시지 큐)들을 가지고 동작할 수 있게 해준다. 로컬 트랜잭션을 사용한다면, 애플리케이션 서버는 트랜잭션 관리에 관여하지 않으며, 다중 자원에 걸쳐 정확함을 보증해주지 않는다(이것은 대부분의 애플리케이션들이 하나의 트랜잭션 자원를 사용하기 때문에 그다지 가치가 없다).

전역 트랜잭션. 전역 트랜잭션은 JTA를 사용하기 위해 필요한 코드에서 명백히 불리한 면을 가진다. 그리고 JTA는 사용하기에 번거로운(부분적으로 예외모델 때문에) API이다. 게다가, JTA UserTransaction은 일반적으로 JNDI를 통해 얻어야만 한다. 이것은 우리가 JTA를 사용하기 위해서는 JNDI JTA 모두 사용해야만 한다는 것을 의미한다. 명백하게 전역 트랜잭션을 사용하는 것은 JTA가 일반적으로 오로지 애플리케이션 서버 환경에서만 가능한 것처럼, 애플리케이션 코드의 재사용성을 제약할 것이다.이전에, 전역 트랜잭션을 사용하기 위해 선호된 방법은 EJB CMT(Container Managed Transaction)를 통하는 것이었다. CMT는 선언적인 트랜잭션 관리(프로그램으로 처리하는 트랜잭션 관리와 구별되는)의 형태이다. EJB CMT는 물론 JNDI의 사용을 요청하는 EJB자체의 사용에도 불구하고 트랜잭션-관련 JNDT룩업을 위한 필요를 제거한다. 이것은 트랜잭션을 제거하기 위한 Java코드를 작성할 필요를 대부분(비록 전체는 아니다) 제거한다. 명백히 불리한 면은 CMT가 JTA와 애플리케이션 서버 환경에 묶인다는 것이다. EJB나 적어도 EJB 트랜잭션성격을 지니는 EJB외관(facade) 뒤에서 비지니스 로직을 구현하는것을 선택한다면 오직 사용가능하다. 대개 EJB에 관련된 부정적인 면은 특히 선언적인 트랜잭션 관리를 위한 대안이라는 측면에서는 매력적인 계획이 아니다.

로컬 트랜잭션. 로컬 트랜잭션은 사용하기가 더 쉽다. 하지만 명백한 단점을 가진다. 그것들은 다중 트랜잭션 성격을 지니는 자원에 대해서 작동할수 없다. 예를 들어, JDBC connection을 사용하여 트랜잭션을 관리하는 코드는 전역 JTA트랜잭션내 작동할수 없다. 다른 부정적인 면은 로컬 트랜잭션이 프로그래밍 모델의 침해적인 경향이라는 것이다.

Spring 프레임워크는 이러한 문제점들을 해결해준다. Spring 프레임워크는 애플리케이션 개발자들로 하여금 어떠한 환경에서라도 일관적인 프로그래밍 모델을 사용할 수 있게 해준다. 당신이 코드를 한 번 작성하면 그것은 다른 환경에서의 다른 트랜잭션 관리 전략에서도 (잘 작동함으로써) 이익을 가져다 줄 것이다. Spring 프레임워크는 선언적/프로그래밍적 트랜잭션 관리 모두 제공한다. 선언적 트랜잭션 관리는 대부분의 사용자들에게 선호되며 대부분의 경우 추천되는 방법이다.

프로그래밍적인 트랜잭션 관리를 하는 개발자들은 어떠한 기반 트랜잭션 하부구조와도 동작할 수 있는 Spring 트랜잭션 추상화로 개발한다. 선호되는 선언적 모델 개발자들은 전형적으로 트랜잭션 관리와 관련된 코딩을 매우 적거나 혹은 아예 하지 않는다. 그리고 Spring (혹은 어떤 다른) 트랜잭션 API에 의존하지 않는다.

9.3. 핵심(key) 추상화

Spring 트랜잭션 추상화의 핵심은 트랜잭션 전략(transaction strategy)에 대한 개념이다. 트랜잭션 전략은 아래에서 보이는 org.springframework.transaction.PlatformTransactionManager인터페이스에 의해 정의된다.

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

비록 이것이 프로그램으로 처리되어 사용될수 있다고 하더라도 기본적으로 SPI(Service Provider Interface) 인터페이스이다. Spring의 철학으로 유지하여, PlatformTransactionManager인터페이스이고 나아가 필요하면 쉽게 mock되거나 stub될수 있다. 더군다나 이것은 JNDI처럼 룩업 전략에 묶이지도 않는다 : PlatformTransactionManager 구현물은 Spring IoC컨테이너에서 다른 객체(또는 bean)처럼 정의된다. 이러한 이득은 심지어 JTA로 작업할때조차도 추상화할 보람이 있도록 해준다는 것이다: 트랜잭션 코드는 직접 JTA를 사용하는 것보다 훨씬 더 쉽게 테스트될 수 있다.

다시 Spring의 철학에서처럼, PlatformTransactionManager인터페이스의 메소드에 의해 던져질수 있는 TransactionException체크되지 않았다(unchecked)(이를테면, 이것은 java.lang.RuntimeException 클래스를 확장한다.). 트랜잭션 하부구조의 실패는 대부분 늘 치명적이다. 애플리케이션 코드가 트랜잭션 실패로부터 복구될 수 있는 아주 드문 경우에는, 애플리케이션 개발자는 여전히 TransactionException을 잡고 핸들링하는 것을 선택할 수 있다. 두드러진 점은 개발자가 그렇게 하도록 강요하지 않는다는 것이다.

getTransaction(...) 메소드는 TransactionDefinition 파라미터에 따라 TransactionStatus 객체를 반환한다. 반환된 TransactionStatus는 아마도 새롭게 생성되거나 (만약 현재의 call스택에 동일한 트랜잭션이 있다면) 존재하고 있는 트랜잭션을 나타낼것이다.

최근 호출 스택내 트랜잭션과 일치한다면, (J2EE 트랜잭션 컨텍스트처럼) TransactionStatus는 실행의 쓰레드와 연관된다.

TransactionDefinition 인터페이스는 명시한다 :

  • 격리성: 이 트랜잭션을 격리하는 정도는 다른 트랜잭션들의 작업으로부터 가진다. 예를 들어, 이 트랜잭션이 다른 트랜잭션들로부터 커밋되지 않은 쓰기작업 내용을 볼 수 있는가?

  • 전파(propagation): 일반적으로 트랜잭션 영역 내에서 실행되는 모든 코드는 그 트랜잭션 내에서 실행될 것이다. 그러나, 만약 트랜잭션 컨텍스트가 이미 존재하는 상황에서 트랜잭션적인 메소드가 실행된다면 그 동작을 지정하는 몇가지 옵션들이 있는데, 예를 들어, (대부분의 경우) 현존하는 트랜잭션 내에서 단순히 실행되기 혹은 현존 트랜잭션을 중지하고 새로운 트랜잭션 생성하기 등이 그것이다. Spring 프레임워크는 EJB CMT로부터 익숙한 트랜잭션 전달 옵션을 제공한다.

  • 타임아웃: 이 트랜잭션이 타임아웃(자동적으로 기반 트랜잭션 하부구조에 의해 롤백되는)되기까지의 시간

  • 읽기전용 상태: 읽기전용 트랜잭션은 어떠한 데이터도 수정하지 않는다. 읽기전용 트랜잭션은 (Hibernate를 사용할 때와 같이) 몇몇 경우에서 유용한 최적화 방식이 될 수 있다.

이러한 세팅들은 기본적인 개념을 반영한다. 만약 필요하다면, 트랜잭션 고립성과 다른 핵심적인 트랜잭션 개념들에 대한 논의자료들을 참조하길 바란다. 그런 핵심 개념들을 이해하는 것은 Spring 혹은 다른 트랜잭션 관리 솔루션을 사용함에 있어서 필수적인 것이다.

TransactionStatus 인터페이스는 트랜잭션 실행과 쿼리 트랜잭션 상태를 제어하기 위한 트랜잭션 코드를 작성하기 위한 간단한 방법을 제공해준다. 모든 트랜잭션 API에 공통적인 것이기 때문에 기본적인 개념은 매우 친숙하게 느껴질 것이다.

public interface TransactionStatus {

    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();
}

Spring에서 선언적이거나 프로그램으로 트랜잭션을 처리하든지, PlatformTransactionManager 구현물을 정의하는 것은 필수이다. 좋은 Spring 형태에서는, 중요한 정의는 의존성삽입을 사용하여 만들어진다.

PlatformTransactionManager 구현물은 대개 작업환경이 JDBC인지, JTA인지, Hibernate인지 등에 대한 지식을 필요로 한다. Spring jPetStore 샘플 애플리케이션의 dataAccessContext-local.xml에서 추출한 다음의 예제는 로컬 PlatformTransactionManager 구현이 정의되는 방법을 보여준다. (이것은 JDBC 환경에서 작동하는 것이다).

우리는 JDBC DataSource를 정의해야만 한다. 그리고나서 DataSource에 대한 참조를 넘겨줌으로써 Spring의 DataSourceTransactionManager를 사용할 것이다.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>

PlatformTransactionManager bean 정의는 다음과 같을 것이다 :

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

동일한 샘플 애플리케이션에 있는 ''dataAccessContext-jta.xml'' 파일에서처럼, 만약 우리가 JTA를 사용한다면 JNDI를 경유해 얻어진 우리는 컨테이너 DataSource를 사용할 필요가 있으며, JtaTransactionManager를 구현해야 한다. JtaTransactionManager는 컨테이너의 전역 트랜잭션 관리 구조를 사용할 것이기 때문에, DataSource 혹은 어떤 다른 자원들에 대해 에 대해 알 필요가 없다.

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

  <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/> 

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
  
  <!-- other <bean/> definitions here -->

</beans>
[Note]Note

'dataSource' bean의 위 정의는 'jee' 명명공간으로 부터 <jndi-lookup/> 태그를 사용한다. 스키마-기반 설정에서 좀더 많은 정보를 위해, Appendix A, XML 스키마-기반 설정를 보라. 그리고 <jee/> 태그에 대해 좀더 많은 정보를 위해, Section A.2.3, “jee 스키마”를 보라.

우리는 Spring PetClinic 샘플 애플리케이션으로부터 다음의 예제를 보는것처럼 Hibernate 로컬 트랜잭션을 쉽게 사용할수 있다. 이 경우, 우리는 애플리케이션 코드가 Hibernate Session 인스턴스를 얻기 위해 사용할 Hibernate LocalSessionFactoryBean을 정의할 필요가 있다.

DataSource bean정의는 위 예제중 하나와 유사할것이다.(이것이 컨테이너 DataSource라면, 컨테이너보다는 Spring처럼 비-트랜잭션 성격을 지닐것이다.)

이 경우 'txManager' bean은 HibernateTransactionManager 클래스이다. 같은 방법으로 DataSourceTransactionManagerDataSource에 대한 참조를 필요로 한다. HibernateTransactionManagerSessionFactory에 대한 참조를 필요로 한다.

<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mappingResources">
    <list>
      <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <value>
	  hibernate.dialect=${hibernate.dialect}
	</value>
  </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

Hibernate와 JTA 트랜잭션을 사용하여, 우리는 JDBC 혹은 어떤 다른 자원 전략들처럼 JtaTransactionManager를 그냥 사용하면 된다.

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

이것은 어떤 트랜잭션 자원에 참여하는 전역 트랜잭션처럼, 어떤 자원을 위한 JTA 설정과 동일하다는 점을 명심하라.

이런 모든 경우에서, 애플리케이션 코드는 아무것도 변경될 필요가 없을 것이다. 우리는 로컬에서 전역 트랜잭션으로 혹은 그 반대의 경우 역시 단지 설정을 바꾸는 것만으로 트랜잭션이 관리되는 방법을 변경할 수 있다.

9.4. 트랜잭션으로 resource 동기화하기

다른 트랜잭션 관리자가 생성되는 방법과 트랜잭션에 동기화될 필요가 있는 관련 resource에 연결할 방법이 명백해야만 한다(이를테면, JDBC DataSourceDataSourceTransactionManager, Hibernate SessionFactoryHibernateTransactionManager 등등). 여기엔 생성/재사용/cleanup과 관련 PlatformTransactionManager를 통한 트랜잭션 동기화의 개념에서 이러한 resource가 임의로 얻어지고 다루는 것을 확실히 하기 위한 애플리케이션이 영속성(persistence) API(JDBC, Hibernate, JDO 등등)를 직접적이나 우회적으로 사용하는 방법에 대한 질문이 남아있다.

9.4.1. 높은 레벨의 접근법

선호하는 접근법은 Spring의 가장 높은 레벨의 영속성(persistence) 통합 API를 사용하는 것이다. 고유(native) API를 대체할뿐 아니라, resource 생성/재사용, cleanup, resource의 선택적인 트랜잭션 동기화 그리고 예외 맵핑을 내부적으로 다룬다. 그래서 사용자 데이터 접근코드는 이러한 사항에 대한 걱정을 전혀 하지 않는다. 하지만 영속성 로직에 전적으로 집중할수 있다. 대개 같은 템플릿 접근법은 다음(JdbcTemplate, HibernateTemplate, JdoTemplate과 같은 클래스)의 모든 영속성 API이다. 이러한 통합 클래스는 이 메뉴얼의 다음 장에서 상세화된다.

9.4.2. 낮은 레벨의 접근법

낮은 레벨에는 DataSourceUtils (JDBC를 위한), SessionFactoryUtils (Hibernate를 위한), PersistenceManagerFactoryUtils (JDO를 위한)와 같은 클래스가 존재한다. 고유 영속성(persistence) API의 resource타입을 가지고 직접적으로 다루기 위한 애플리케이션 코드를 선호할때, 이러한 클래스는 Spring 프레임워크-관리 인스턴스가 얻어지고, 트랜잭션이 동기화되고, 영속성 API에 맵핑되는 프로세스에서 발생하는 예외를 확인한다.

예를 들어, JDBC를 위해, DataSource에서 getConnection()메소드를 호출하는 전통적인 JDBC접근법 대신에, 당신은 다음처럼 Spring의 org.springframework.jdbc.datasource.DataSourceUtils를 대신 사용할것이다.

Connection conn = DataSourceUtils.getConnection(dataSource);

만약 트랜잭션이 존재하고 동기화된 connection을 가진다면, 인스턴스는 반환될것이다. 반면에, 메소드 호출은 존재하는 트랜잭션에 (선택적으로)동기화되고 같은 트랜잭션내 재사용하기 위해 사용가능한 새로운 connection의 생성을 처리할것이다. 언급된것처럼, 이것은 SQLException이 체크되지 않은 DataAccessExceptions의 Spring구조중 하나인 CannotGetJdbcConnectionException로 포장되는 추가된 장점을 가진다. 이것은 당신에게 SQLException으로 부터 쉽게 얻고 데이터베이스간의(다른 영속성 기술간의) 이식성을 확실시할수 있는 좀더 많은 정보를 줄것이다.

이것은 Spring트랜잭션 관리 없이도 잘 작동한다. 그래서 당신은 트랜잭션 관리를 위해 Spring을 사용하든지 말든지 이것을 사용할수 있다.

물론, 당신이 Spring의 JDBC지원이나 Hibernate지원을 사용했을때, 관련 API에 직접적으로 작업하기 보다는 Spring추상화를 통해 좀더 좋게 작업할수 있기 때문에, 당신은 대개 DataSourceUtils나 다른 헬퍼 클래스를 사용하지 않는것을 선호할것이다. 예를 들어, 당신이 JDBC사용을 단순화하기 위해 Spring JdbcTemplate이나 jdbc.object패키지를 사용한다면, 정확한 connection 복구(retrieval)가 발생하고 당신은 어떤 특별한 코드를 작성할 필요가 없을것이다.

9.4.3. TransactionAwareDataSourceProxy

매우 낮은 레벨에 TransactionAwareDataSourceProxy클래스가 존재한다. 이것은 Spring관리 트랜잭션의 인지를 추가하는 목표 DataSource를 위한 프록시이다. 이 점에서 J2EE서버가 제공하는 전통적인 JNDI DataSource와 유사하다.

호출되어야만 하는 코드가 존재하고 표준 JDBC DataSource인터페이스 구현물이 전달될때를 제외하고 이 클래스를 사용하는 것이 결코 필요하거나 바람직하지는 않다. 이 경우, 재사용될수 있는 코드를 가지는 것은 가능하다. 하지만 Spring관리 트랜잭션내 포함된다. 위에서 언급된 더 높은 레벨의 추상화를 사용하여 당신 자신의 새로운 코드를 작성하는 것이 선호된다.

9.5. 선언적인 트랜잭션 관리

대부분의 Spring사용자는 선언적인 트랜잭션 관리를 선택한다. 이것은 애플리케이션 코드의 가장 최소의 영향을 가지는 것은 선택사항이고 나아가 비-침략적 경량 컨테이너의 목표를 가지고 일관적이다.

Spring의 선언적인 트랜잭션 관리는 Spring AOP로 가능하다. 비록, 트랜잭션 성격을 가지는 aspect가 Spring에서 나오고 진부한 반복형태로 사용된다고 하더라도, AOP개념은 이 코드의 효과적인 사용을 위해 대개 이해되지 않는다.

EJB CMT를 검토하여 시작하고 Spring 선언적인 트랜잭션 관리의 유사함과 차이점을 설명하는 것이 도움이 된다. 기본적인 접근법은 유사하다. 트랜잭션 행위를 개별 메소드 레벨로 명시하는 것은 가능하다. 필요하다면 트랜잭션 컨텍스트내 setRollbackOnly()를 호출하는 것이 가능하다. 차이점은 :

  • JTA에 묶이는 EJB CMT와는 달리, Spring 선언적인 트랜잭션 관리는 어떠한 환경에서도 작동한다. 이것은 설정상의 변경만을 하면서 JDBC, JDO, Hibernate 또는 다른 트랜잭션과 작동할수 있다.

  • Spring 프레임워크는 EJB와 같이 특정 클래스에만이 아닌 선언적인 트랜잭션 관리가 어느 클래스에 적용되는것이 가능하다.

  • Spring 프레임워크는 선언적인 롤백 규칙을 제공한다. EJB가 없는 기능은 우리가 아래에서 언급할것이다. 롤백은 프로그램으로가 아닌 선언적으로 제어될수 있다.

  • Spring 프레임워크는 AOP를 사용하여, 트랜잭션 성격을 지니는 행위를 사용자정의하는 기회를 준다. 예를 들어, 트랜잭션 롤백의 경우 사용자정의 행위를 추가하길 원한다면, 당신은 할수 있다. 당신은 트랜잭션 성격을 지니는 advice에 따라 임의의 advice를 추가할수 있다. EJB CMT로, 당신은 setRollbackOnly()보다 컨테이너의 트랜잭션 관리에 영향을 줄 방법을 가지지 않는다.

  • Spring 프레임워크는 높은 수준의 애플리케이션 서버가 하는 것처럼 원격 호출에 대해 트랜잭션 컨텍스트의 위임을 지원하지 않는다. 이 기능이 필요하다면, 우리는 EJB를 사용하도록 추천한다. 어쨌든, 이러한 기능을 사용하기전에 주의깊게 검토하라. 대개, 우리는 원격 호출을 확장하기 위한 트랜잭션을 원하지 않는다.

롤백 규칙의 개념은 중요하다. 그것들은 우리에게 예외가 자동 롤백을 야기하는 것을 명시하는 것을 가능하게 한다. 우리는 이것을 Java코드가 아닌 설정에서 선언적으로 명시한다. 그래서 우리가 현재 트랜잭션을 프로그램으로 롤백하기 위해 TransactionStatus객체에서 여전히 setRollbackOnly() 를 호출할수 있다. 우리는 MyApplicationException이 결과적으로 언제나 롤백하는 규칙을 명시할수 있다. 이것은 비지니스 객체가 트랜잭션 하위구조에 의존할 필요가 없는 명백한 장점을 가진다. 예를 들어, 그것들은 Spring API를 import할 필요가 없다.

EJB 디폴트 행위는 시스템 예외(언제나 런타임 예외인)에서 트랜잭션을 자동적으로 롤백하기 위한 EJB컨테이너를 위하는 동안, EJB CMT는 애플리케이션 예외(이를테면, java.rmi.RemoteException보다는 체크된 예외)에서 자동적으로 트랜잭션을 롤백하지 않는다. 선언적인 트랜잭션 관리를 위한 Spring 디폴트 행위가 EJB규칙(체크되지 않은 예외에서만 자동적으로 롤백이 된다)을 따르는 동안, 이것은 종종 사용자정의하는 것이 유용하다.

9.5.1. Spring의 선언적인 트랜잭션 구현물을 이해하기

이 부분의 목적은 때때로 선언적인 트랜잭션의 사용과 관련된 신비함을 풀어내는 것이다. @Transactional 어노테이션을 가지고 당신의 클래스를 추석처리하기 위한 참조문서이다. 당신의 설정에 ('<tx:annotation-driven/>')를 추가하고 이것이 작동하는 방법을 이해하길 바란다. This section will explain the inner workings of Spring's declarative transaction infrastructure to help you navigate your way back upstream to calmer waters in the event of transaction-related issues.

[Tip]Tip

Spring 소스코드를 보는 것은 Spring이 트랜잭션 지원에 대해 실제로 이해하기 위한 좋은 방법이다. 당신은 Javadoc정보를 완벽하게 찾을수 있다. 우리는 개발기간동안 어떻게 이루어지는지 좀더 제대로 보기 위해 Spring-가능한 애플리케이션에서 로깅레벨을 'DEBUG'로 두기를 제안한다.

Spring의 선언적인 트랜잭션 지원에 관련하여 가장 중요한 개념은 이 지원이 AOP 프록시를 통해서만 가능하고, 트랜잭션 성격을 가지는 advice는 메타데이타(XML- 이나 어노테이션-기반의)에 의해 다루어진다는 것이다. 트랜잭션 성격을 가지는 메타데이타를 가진 프록시의 조합은 메소드 호출에 대해 트랜잭션을 다루는 적절한 PlatformTransactionManager 구현물과 함께 TransactionInterceptor를 사용하는 AOP프록시를 만들어낸다.

[Note]Note

비록 AOP의 지식은 Spring의 선언적인 트랜잭션 지원을 사용하기 위해 필수는 아니다. 이것을 도울수 있을 뿐이다. Spring AOP는 Chapter 6, Spring을 이용한 Aspect 지향 프로그래밍에서 전반적으로 다루어진다.

개념적으로, 트랜잭션 성격을 가지는 프록시에서 메소드를 호출하는 것은 다음과 같을것이다.

9.5.2. 첫번째 예제

다음의 인터페이스와 관련 구현물을 보라. 의도는 개념을 전달한다. FooBar를 사용하는 것은 당신이 트랜잭션 사용에 집중하고 도메인 모델에 대해 걱정할 필요가 없다는 것을 의미한다.

<!-- the service interface that we want to make transactional -->
            
package x.y.service;
            
public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
<!-- an implementation of the above interface -->
            
package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }
}

(이 예제의 목적을 위해, 사실 구현물 클래스 (DefaultFooService)는 각각의 구현된 메소드의 몸체에서 UnsupportedOperationException을 던진다. 이것은 생성된 트랜잭션을 보도록 허용하고 던져지는 UnsupportedOperationException 인스턴스에 대한 응답으로 롤백한다.

FooService 인터페이스의 첫번째 두개의 메소드(getFoo(String)getFoo(String, String))가 읽기전용으로 트랜잭션내에서 수행된다고 가정해보자. 그리고 다른 메소드(insertFoo(Foo)updateFoo(Foo))는 읽고 쓰기가 가능한 트랜잭션내 수행된다고 가정한다.

XML-기반의 메타데이타를 사용하여 선언적인 형태로 이 상황을 설정하기 위해, 당신은 다음의 설정을 작성할 것이다.

<!-- from the file 'context.xml' -->
<?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: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-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
  
  <!-- this is the service object that we want to make transactional -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->
  <tx:advice id="txAdvice" transaction-manager="txManager">
    <!-- the transactional semantics... -->
    <tx:attributes>
      <!-- all methods starting with 'get' are read-only -->
      <tx:method name="get*" read-only="true"/>
      <!-- other methods use the default transaction settings (see below) -->
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>
  
  <!-- ensure that the above transactional advice runs for any execution
      of an operation defined by the FooService interface -->
  <aop:config>
    <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
  </aop:config>
  
  <!-- don't forget the DataSource -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
    <property name="username" value="scott"/>
    <property name="password" value="tiger"/>
  </bean>

  <!-- similarly, don't forget the (particular) PlatformTransactionManager -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
  </bean>
  
  <!-- other <bean/> definitions here -->

</beans>

위 설정을 신중하게 보자. 우리는 트랜잭션 성격을 가지길 원하는 서비스 객체('fooService' bean)를 가진다. 우리가 적용하길 원하는 트랜잭션 의미는 <tx:advice/>정의에 캡슐화된다. <tx:advice/>정의는 “'get'으로 시작하는 모든 메소드는 읽기전용 트랜잭션에서 수행하고 다른 모든 메소드는 디폴트 트랜잭션으로 수행된다.” 라고 읽는다. <tx:advice/> 태그의 'transaction-manager' 속성은 트랜잭션을 실제로 다루는(이 경우 'txManager' bean) PlatformTransactionManager bean의 이름으로 셋팅된다.

[Tip]Tip

PlatformTransactionManager의 bean이름이 'transactionManager' 이름을 가져서 묶이길 원한다면 당신은 실제로 트랜잭션 성격을 가지는 advice(<tx:advice/>) 내 'transaction-manager' 속성을 생략할수 있다. PlatformTransactionManager bean이 다른 이름을 가져서 묶이길 원한다면, 명시적으로 가지고 위 예제처럼, 'transaction-manager' 속성을 사용한다.

설정의 마지막은 <aop:config/> 정의이다. 이것은 'txAdvice' bean에 의해 정의되는 트랜잭션 성격을 가지는 advice가 애플리케이션내 적절한 지점에서 수행된다는 것을 확신한다. 첫번째 우리는 FooService 인터페이스에 정의된 어떤 작업의 수행에 일치하는 pointcut(우리는 이 pointcut 'fooServiceOperation'을 말한다.)를 정의한다. 그리고나서 우리는 advisor를 사용하여 'txAdvice'를 가진 pointcut을 조합한다. 결과는 'fooServiceOperation' 수행에 표시한다. 'txAdvice'에 의해 정의된 advice는 작동할것이다.

<aop:pointcut/>요소내 정의된 표현은 AspectJ pointcut표현이다. Spring 2.0내 pointcut표현에 대한 좀더 상세한 정보를 위해서는 Chapter 6, Spring을 이용한 Aspect 지향 프로그래밍를 보라.

공통의 요구사항은 전체 서비스 레이어를 트랜잭션 성격을 가지도록 만드는 것이다. 이것을 하기 위해 가장 좋은 방법은 서비스 레이어내 어떤 작업(operation)에 일치하는 pointcut표현을 간단히 변경하는 것이다. 예를 들면 :

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
  </aop:config>

(이 예제는 당신의 모든 서비스 인터페이스가 'x.y.service' 패키지내 정의된다고 가정한다. 다시 말해. 좀더 상세한 정보를 위해서는 Chapter 6, Spring을 이용한 Aspect 지향 프로그래밍를 보라.)

지금 우리는 설정을 꼼꼼히 살펴보고 있다. 당신은 스스로에게 물어볼지도 모른다. “좋아.. 하지만 이 설정 모두가 실제로 무엇을 하는가.?”.

위 설정이 하는것은 'fooService' bean 정의로부터 생성된 객체에 대해 트랜잭션 성격을 가지는 프록시를 생성하는것이다. 프록시는 트랜잭션 성격을 가지는 advice로 설정될것이다. 그래서 적절한 메소드가 프록시에서 호출될때, 트랜잭션은 메소드에 관련된 트랜잭션에 의존하여 아마도 시작되고, 일시정지하고, 읽기전용으로 표시된다.

위 설정을 테스트하기 위한 다음의 드라이버 프로그램을 보자.

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
        FooService fooService = (FooService) ctx.getBean("fooService");
        fooService.insertFoo (new Foo());
    }
}

위 프로그램을 실행하여 얻은 출력은 다음과 같을것이다. (Log4J출력과 DefaultFooService 클래스의 insertFoo(..) 메소드에 의해 던져진 UnsupportedOperationException으로부터 관련 스택추적 메시지는 명백하게 짤린다.)

    <!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy
        for bean 'fooService' with 0 common interceptors and 1 specific interceptors
    <!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

    <!-- ... the insertFoo(..) method is now being invoked on the proxy -->

[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
    <!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection
        [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

    <!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should
        rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo
        due to throwable [java.lang.UnsupportedOperationException]

   <!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection
        [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException
	at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
   <!-- AOP infrastructure stack trace elements removed for clarity -->
	at $Proxy0.insertFoo(Unknown Source)
	at Boot.main(Boot.java:11)

9.5.3. 롤백

The previous section outlined the basics of how to specify the transactional settings for the classes, typically service layer classes, in your application in a declarative fashion. This section describes how you can control the rollback of transactions in a simple declarative fashion.

The easiest (and indeed recommended) way to indicate to the Spring Framework's transaction infrastructure that a transaction's work is to be rolled back is to throw an Exception from code that is currently executing in the context of a transaction. The Spring Framework's transaction infrastructure code will catch any unhandled Exception as it bubbles up the call stack, and will mark the transaction for rollback.

However, please note that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception an instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback.) Checked exceptions that are thrown from a transactional method will not result in the transaction being rolled back.

So much for the default settings; these settings, namely exactly which Exception types mark a transaction for rollback, can of course be changed. Find below a snippet of XML configuration that demonstrates how one would configure rollback for a checked, application-specific Exception type.

<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
	 <tx:method name="get*" read-only="false" rollback-for="NoProductInStockException"/>
	 <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

The second way to indicate to the Spring Framework's transaction infrastructure that a transaction's work is to be rolled back is to do so programmatically Although very simple, this way is quite invasive, and tightly couples your code to the Spring Framework's transaction infrastructure. Find below a snippet of code that does programmatic rollback of a Spring Framework-managed transaction:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

You are strongly encouraged to use the declarative approach to rollback if at all possible. The programmtic means of rolling back is, as you can see, available to you should you need it, but it's usage flies in the face of achieving a clean POJO-based model in your application.

9.5.4. 다른 bean에 다른 트랜잭션 성격을 가지는 의미를 설정하기

많은 서비스 레이어 객체를 가지고 이러한 객체의 각각의 그룹에 전체적으로 다른 트랜잭션의 의미를 적용하길 원하는 상황을 생각해보자. 당신은 'pointcut''advice-ref' 속성값을 가지는 많은 수의 다른 <aop:advisor/>요소를 정의하여 Spring에 영향을 끼칠수 있다.

예제의 방법으로, 모든 서비스 레이어 클래스가 가장 상위 'x.y.service' 패키지내 정의된다고 가정하자. 클래스의 인스턴스인 모든 bean을 만들기 위해 언급된 패키지내 정의하고 디폴트 트랜잭션의 의미를 가지는 'Service'로 끝나는 이름을 가진 bean을 만들기 위해 당신은 다음의 설정을 작성할수 있다.

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

    <aop:config>

        <aop:pointcut id="serviceOperation"
                    expression="execution(* x.y.service..*Service.*(..))"/>

        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

    </aop:config>

    <!-- these two beans will be transactional... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    <!-- ... and these two beans won't -->
    <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->

    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

아래는 전체적으로 다른 트랜잭션 셋팅을 가지고 두개의 구별되는 bean을 설정하는 예제이다.

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

    <aop:config>

        <aop:pointcut id="defaultServiceOperation"
                    expression="execution(* x.y.service.*Service.*(..))"/>
        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
        
        <aop:pointcut id="noTxServiceOperation"
                    expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

    </aop:config>

    <!-- this bean will be transactional (c.f. the 'defaultServiceOperation' pointcut) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this bean will also be transactional, but with totally different transactional settings -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
        </tx:attributes>
    </tx:advice>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

9.5.5. <tx:advice/> settings

This section summarises the various transactional settings that can be specified using the <tx:advice/> tag. The default <tx:advice/> settings are:

  • The propagation setting is REQUIRED

  • The isolation level is DEFAULT

  • The transaction is read/write

  • The transaction timeout defaults to the default timeout of the underlying transaction system, or or none if timeouts are not supported

  • Any RuntimeException will trigger rollback, and any checked Exception will not

These default settings can, of course, be changed; the various attributes of the <tx:method/> tags that are nested within <tx:advice/> and <tx:attributes/> tags are summarized below:

Table 9.1. <tx:method/> settings

AttributeRequired?DefaultDescription
nameYes 

The method name(s) with which the transaction attributes are to be associated. The wildcard (*) character can be used to associate the same transaction attribute settings with a number of methods; for example, 'get*', 'handle*', 'on*Event', etc.

propagationNoREQUIREDThe transaction propagation behavior
isolationNoDEFAULTThe transaction isolation level
timeoutNo-1The transaction timeout value (in seconds)
read-onlyNofalseIs this transaction read-only?
rollback-forNo 

The Exception(s) that will trigger rollback; comma-delimited. For example, 'com.foo.MyBusinessException,ServletException'

no-rollback-forNo 

The Exception(s) that will not trigger rollback; comma-delimited. For example, 'com.foo.MyBusinessException,ServletException'

9.5.6. @Transactional 사용하기

[Note]Note

@Transactional어노테이션에 의해 제공되는 기능과 관련된 지원 클래스는 당신이 적어도 Java 5를 사용할때만 사용가능하다.

트랜잭션 설정에 대해 XML기반의 선언적인 접근법에 추가적으로, 당신은 트랜잭션 설정에 대해 어노테이션-기반의 선언적인 접근법을 사용할수 있다.

Java소스코드내 직접적으로 트랜잭션 구문을 선언하는 것은 선언을 영향을 끼치는 코드에 좀더 근접하게 두는것이다. 불필요한 커플링의 위험이 더 많지 않다. 트랜잭션 성질을 가지면서 배치되는 코드는 언제나 이러한 방법으로 배치된다.

@Transactional 어노테이션의 사용으로 사용하기 쉬운것은 예제에서 가장 잘 표현되었다. 다음의 인터페이스 정의를 보라.

<!-- the service class that we want to make transactional -->
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

Spring 컨테이너내 bean처럼 정의된 위 POJO는 XML설정에서 줄을 추가하여 트랜잭션 성질을 가질수 있다.

<!-- from the file 'context.xml' -->
<?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: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-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
  
  <!-- this is the service object that we want to make transactional -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- enable the configuration of transactional behavior based on annotations -->
  <tx:annotation-driven transaction-manager="txManager"/>

  <!-- a PlatformTransactionManager is still required -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- (this dependency is defined somewhere else) -->
    <property name="dataSource" ref="dataSource"/>
  </bean>
  
  <!-- other <bean/> definitions here -->

</beans>
[Tip]Tip

당신이 묶기를 원하는 PlatformTransactionManager의 bean이름이 'transactionManager'이름을 가진다면 <tx:annotation-driven/> 태그내 'transaction-manager' 속성을 생략할수 있다. 당신이 묶길 원하는 PlatformTransactionManager bean이 다른 이름을 가진다면, 명시적이거나 위 예제에서처럼 'transaction-manager' 속성을 사용해야만 한다.

@Transactional 어노테이션은 인터페이스 정의, 인터페이스의 메소드, 클래스 정의, 또는 클래스의 public 메소드 앞에 둘것이다. 단지 @Transactional 어노테이션의 존재로는 트랜잭션 성격을 가지는 행위를 활성화하기에는 충분하지 않다. @Transactional 어노테이션은 @Transactional을 인식하는 어떤것에 의해 소비될수 있고 트랜잭션 성격을 가지는 행위를 적용하기 위한 트랜잭션 성격을 가지는 메타데이타를 사용할수 있는 간단한 메타데이타이다. 위 예제의 경우, 이것은 트랜잭션 성격을 가지는 행위를 교체하는 <tx:annotation-driven/> 요소의 존재이다.

The Spring team's recommendation is that you place the @Transactional annotation on the concrete class (or method of a concrete class), as opposed to on any interface(s) that the class may implement. You certainly can place the @Transactional annotation on an interface, but this will only work as you would expect it to if you are using interface-based proxies. Because annotations are not inherited it means that if you are using class-based proxying then the transaction settings will not be recognised by the class-based proxying infrastructure and the object will not be wrapped in a transaction proxy (which would be decidedly bad). So please do take the Spring teams's advice and use the @Transactional annotation on concrete classes.

[Note]Note

When using the @Transactional style of declarative transaction demarcation you can control whether or not interface- or class-based proxies are created via the presence and value of the "proxy-target-class" attribute on the attendant <tx:annotation-driven/> element. If the value of the boolean-style value of the "proxy-target-class" attribute is set to "true", then class-based proxying will be in effect (and this currently requires the presence of the CGLIB library (cglib.jar) on the classpath). If the value of the "proxy-target-class" attribute is set to "false" or if the attribute is omitted, then standard JDK interface-based proxying will be in effect.

The most derived location takes precedence when evaluating the transactional settings for a method. In the case of the following example, the DefaultFooService class is annotated with the settings for a read-only transaction, but the @Transactional annotation on the updateFoo(Foo) method in the same class takes precedence over the transactional settings defined in the class level annotation.

아래와 비교..??

Spring 프레임워크의 중심적인 주의(tenets)를 유지한체, 상속에 관련하여 Spring의 @Transactional 어노테이션의 처리는 이해된다. @Transactional 어노테이션을 가지고 클래스 레벨에서 인터페이스를 주석처리한다면, 인터페이스의 모든 구현물은 인터페이스에 적용되는 트랜잭션 셋팅을 상속할 것이다. 이것은 결코 상속되지 않는 인터페이스와 메소드의 어노테이션에 적용하는 대개의 구문에 대한 직접적인 반대개념이다. Spring으로, 당신은 자체적인 @Transactional 값을 명시하여 수퍼클래스나 인터페이스로부터 상속된 디폴트 트랜잭션 셋팅을 오버라이드할수 있다. 기본적으로, 대개의 유래된 위치는 메소드의 트랜잭션 의미를 평가할때 우선권을 가진다. 다음의 예제의 경우, FooService 인터페이스는 디폴트 트랜잭션 셋팅을 가지고 주석처리되지만, DefaultFooService 클래스의 updateFoo(Foo)에 있는 @Transactional 어노테이션은 FooService 인터페이스로부터 상속된 트랜잭션 성격을 가지는 셋팅에 대해 우선권을 가진다.

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

9.5.6.1. @Transactional 셋팅

@Transactional 어노테이션은 트랜잭션 성격을 가지는 인터페이스, 클래스 나 메소드를 명시하는 메타데이터이다. 예륻 들어, “이 메소드가 호출되고 존재하는 트랜잭션을 다시 실행할때 새로운 읽기전용 트랜잭션을 새기는 것을 시작한다.”. 디폴트 @Transactional 셋팅은

  • 위임 셋팅은 PROPAGATION_REQUIRED이다.

  • 격리 레벨은 ISOLATION_DEFAULT이다.

  • 트랜잭션은 읽기/쓰기이다.

  • 트랜잭션 타임아웃은 참조하는 트랜잭션 시스템의 디폴트 타임아웃에 디폴트하거나 타임아웃이 지원되지 않는다면 없다.

  • 어떤 RuntimeException 이 롤백하고 어떤 체크된 Exception은 그렇지 않다.

이러한 디폴트 셋팅은 물론 변경될수 있다. @Transactional 어노테이션의 다양한 프라퍼티는 다음 테이블에서 개요화된다.

트랜잭션 셋팅을 변경하는 어노테이션의 선택적인 프라퍼티

Table 9.2. @Transactional 프라퍼티

프라퍼티타입상세설명
위임(propagation)enum: Propagation선택적인 위임 셋팅
격리(isolation)enum: Isolation선택적인 위임 레벨
읽기전용(readOnly)boolean읽기/쓰기 대 읽기전용 트랜잭션
타임아웃(timeout)int (초단위)트랜잭션 타임아웃
rollbackForThrowable로부터 유래되는 Class 객체의 배열롤백을 야기해야하는 예외 클래스의 선택적인 배열.
rollbackForClassname 클래스명의 배열. Throwable로부터 유래되는 클래스롤백을 야기해야하는 예외 클래스 이름의 선택적인 배열
noRollbackForThrowable로부터 유래된 Class 객체의 배열롤백을 야기하지 않는 예외 클래스의 선택적인 배열.
noRollbackForClassnameThrowable로부터 유래되는 String 클래스 명의 배열롤백을 야기하지 않는 예외 클래스의 선택적인 배열.

우리는 위 프라퍼티와 관련값을 좀더 상세하게 언급하는 @Transactional 어노테이션을 위한 Javadoc을 보길 권한다.

9.5.7. 트랜잭션 성격을 가지는 작업에 충고하기(advise)

클래스의 인스턴스를 가지는 상황이라고 생각해보자. 그리고 당신은 트랜잭션 성격을 가지고 몇가지 기본적인 프로파일 advice를 수행하길 원할것이다. <tx:annotation-driven/>를 사용하는 면에서 어떻게 영향을 주는가.?

우리가 updateFoo(Foo) 메소드를 호출할때 보길 원하는 것은

  • 설정된 프로파일링 aspect가 시작되고,

  • 트랜잭션 성격을 가지는 advice가 수행되고,

  • advised객체의 메소드가 수행되고

  • 트랜잭션이 커밋되고,

  • 프로파일링 aspect가 전체 메소드 호출이 소요하는 시각이 정확이 어떻게 되는지 리포팅한다

[Note]Note

이 장은 설명된 AOP와 연관되지 않는다(트랜잭션에 적용되는것을 제외하고). 다음의 AOP설정의 일부를 상세히 다루기 위해 Chapter 6, Spring을 이용한 Aspect 지향 프로그래밍 를 보라.

이것은 간단한 프로파일링 aspect를 위한 코드이다. (advice의 순서는 Ordered 인터페이스를 통해 제어된다. advice순서에 대한 완전한 상세설명을 위해, Section 6.2.4.7, “Advice ordering”를 보라.)

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

    private int order;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    // this method is the around advice
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}

이것은 우리가 원하는 영향을 줄 관련 설정이다.

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

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this is the aspect -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>
    
    <tx:annotation-driven transaction-manager="txManager"/>

    <aop:config>
        <!-- this advice will execute around the transactional advice -->
        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                          expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>
    </aop:config>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

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

</beans>

위 설정의 결과는 프로파일링된 'fooService' bean와 순서대로 이것에 적용된 트랜잭션 성격을 가지는 aspect일것이다. 추가적인 aspect의 설정은 유사한 형태로 영향을 끼친다.

마지막으로, XML 선언 접근법을 사용하지만 위와 같은 셋업에 영향을 끼치기 위한 예제 설정을 아래에서 보라.

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

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

    <aop:config>
        
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>

        <!-- will execute after the profiling advice (c.f. the order attribute) -->
        <aop:advisor
                advice-ref="txAdvice"
                pointcut-ref="entryPointMethod"
                order="2"/> <!-- order value is higher than the profiling aspect -->

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                          expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

위 설정의 결과는 프로파일링된 'fooService' bean와 순서대로 이것에 적용된 트랜잭션 성격을 가지는 aspect일것이다. 추가적인 aspect의 설정은 유사한 형태로 영향을 끼친다. 입구에서 트랜잭션 성격을 가지는 advice 에서와 출구에서 트랜잭션 성격을 가지는 advice 이전에 수행할 프로파일링 advice를 원한다면, 트랜잭션 성격을 가지는 advice의 order값보다 더 큰 값과 같이 프로파일링 aspect bean의 'order' 프라퍼티를 간단히 교체할것이다.

많은 수의 추가적인 aspect의 설정은 유사한 형태로 영향을 끼친다.

9.5.8. AspectJ와 @Transactional 를 사용하기

AspectJ aspect의 방법에 의해 Spring 컨테이너의 외부에서 Spring의 @Transactional 지원을 사용하는 것은 가능하다. 이 지원을 사용하기 위해 당신은 먼저 타입과 @Transactional 어노테이션을 가진 메소드를 주석처리하고 spring-aspects.jar파일내 정의된 org.springframework.transaction.aspectj.AnnotationTransactionAspect 를 가진 애플리케이션을 연결해야만 한다. aspect는 트랜잭션 관리자로 설정되어야만 한다. 당신은 aspect를 의존성 삽입하기 위해 Spring을 사용할수 있지만 우리는 Spring컨테이너 외부에서 구동중인 애플리케이션을 여기서 집중할것이다. 우리는 당신에게 프로그램으로 처리하는 방법을 보여줄것이다.

[Note]Note

계속하기 전에, 당신은 Section 9.5.6, “@Transactional 사용하기”Chapter 6, Spring을 이용한 Aspect 지향 프로그래밍를 각각 보길 원할것이다.

// construct an appropriate transaction manager 
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager (txManager); 

이 aspect를 사용할때, 당신은 클래스가 구현하는 인터페이스가 아닌, 구현 클래스(그리고/또는 클래스내 메소드)를 주석처리(annotate)해야만 한다. AspectJ는 인터페이스의 주석이 상속되지 않는 Java의 규칙을 따른다.

클래스의 @Transactional어노테이션은 클래스내 어떠한 public 작업의 수행을 위한 디폴트 트랜잭션 구문을 명시한다.

클래스내 메소드의 @Transactional 어노테이션은 클래스 어노테이션에 의해 주어진 디폴트 트랜잭션 구문을 오버라이드한다. public, protected, 그리고 default 가시성(visibility)을 가진 메소드는 어노테이션처리되어야 할것이다. protected와 default 가시성을 가진 메소드를 직접 어노테이션 처리하는 것은 이러한 작업의 수행을 위한 트랜잭션 구문을 얻기 위한 유일한 방법이다.

이 aspect를 사용할때 @Transactional 어노테이션은 트랜잭션 성격을 가지도록 타입이나 인터페이스를 주석처리하기 위해 사용될수 있다. 그리고 순차적으로 그것에 의해 정의된 어떤 public작업의 수행은 트랜잭션 성격의 의미를 가질것이다. 개별 public 메소드를 위한 트랜잭션 성격을 가지는 의미는 관련 메소드 정의에 주석처리를 함으로써 정의될수 있다. 인터페이스 멤버가 주석처리가 된다면(인터페이스를 구현하는 메소드에 반대되는), 인터페이스 자체는 @Transactional 처럼 주석처리될것이다.

AnnotationTransactionAspect로 애플리케이션을 묶거(weave)나 로드시 묶는것을 사용하기 위해 당신은 AspectJ(AspectJ 개발 가이드를 보라)로 애플리케이션을 빌드해야만 한다. AspectJ를 사용한 로드시 묶기에 대해서 Section 6.8.4, “Using AspectJ Load-time weaving (LTW) with Spring applications” 를 보라.

9.6. 프로그램으로 처리하는 트랜잭션 관리

Spring 프로그래밍적인 트랜잭션 관리에 있어 두 가지 방법을 제시한다.

  • TransactionTemplate을 사용하기

  • 직접 PlatformTransactionManager 구현물을 사용하기

당신이 프로그램으로 처리하는 트랜잭션 관리를 사용한다면, 우리는 일반적으로 전자의 접근방법(이를테면, TransactionTemplate를 사용하여)을 추천한다. 두 번째 접근법은 (비록 예외처리는 덜 성가시지만) JTA UserTransaction API의 사용과 비슷하다.

9.6.1. TransactionTemplate 사용하기

TransactionTemplateJdbcTemplateHibernateTemplate와 같은 다른 Spring templates와 동일한 접근 방식을 적용하고 있다. 이것은 콜백(callback) 접근방법을 사용하는데, 자원 획득과 해제작업으로부터 애플리케이션 코드를 해방시켜준다.(더이상 try/catch/finally를 할 필요가 없다). Spring내 다른 템플릿 클래스처럼, TransactionTemplate는 쓰레드 안전하다.

트랜잭션 컨텍스트 내에서 실행되어야 하는 애플리케이션 코드는 다음과 같을 것이다. TransactionCallback이 값을 반환하기 위해 사용되는 부분에 주목하라.

Object result = tt.execute(new TransactionCallback() {

    public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
    }

});

만약 반환될 값이 없다면, 다음과 같이 익명 클래스를 통해 TransactionCallbackWithoutResult를 사용하라.

tt.execute(new TransactionCallbackWithoutResult() {
    
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }

});

콜백 내의 코드는 제공되는 TransactionStatus 객체의 setRollbackOnly() 메소드를 호출함으로써 트랜잭션을 롤백할 수 있다.

TransactionTemplate를 사용하려는 애플리케이션 클래스들은 반드시 PlatformTransactionManager에 접근해야만 한다(의존성 삽입을 통해 클래스에 제공될). 이것은 mock 혹은 stub PlatformTransactionManager와 같은 클래스를 단위 테스트 하기는 쉽다. 여기에는 JNDI 룩업 혹은 정적인 마법이 존재하지 않는다 : 이것은 단순한 인터페이스이다. 대개, 당신은 유닛 테스트를 간단하게 만들기 위해 Spring을 사용할 수 있다.

9.6.2. PlatformTransactionManager 사용하기

당신은 트랜잭션을 직접 관리하기 위해 org.springframework.transaction.PlatformTransactionManager도 역시 사용할 수 있다. bean참조를 통해 bean을 사용하여 PlatformTransactionManager의 구현물을 간단히 전달하라.그리고나서, TransactionDefinitionTransactionStatus 객체를 사용함으로써, 당신은 트랜잭션을 초기화하고, 롤백, 커밋할 수 있다.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

9.7. 프로그램으로 처리하는 것과 선언적인 트랜잭션 관리간의 선택하기

프로그램으로 처리하는 트랜잭션 관리는 당신이 적은 수의 트랜잭션 성격을 가지는 작업을 가진다면 언제나 좋은 생각이다. 예를 들어, 업데이트 작업을 위한 트랜잭션을 요구하는 웹 애플리케이션을 가진다면, 당신은 Spring이나 다른 기술을 사용하여 트랜잭션 성격을 가지는 프록시를 셋업하길 원하지는 않을것이다. 이 경우, TransactionTemplate를 사용하는 것이 아마 좋은 접근법일것이다.

반면에, 애플리케이션이 많은 트랜잭션 성격을 가지는 작업을 가진다면, 선언적인 트랜잭션 관리가 가치가 있다. 이것은 비지니스 로직외부에서 트랜잭션 관리를 유지하고 Spring에서는 설정하기가 어렵지 않다. EJB CMT보다 Spring을 사용하는 것이 선언적인 트랜잭션 관리의 설정 비용이 크게 감소한다.

9.8. 특정 애플리케이션 서버에 대한 통합

Spring의 트랜잭션 추상화는 대개 애플리케이션 서버에 대해 관용적이다. 추가적으로, JTA UserTransactionTransactionManager객체를 위한 JNDI룩업을 선택적으로 수행할수 있는 Spring의 JtaTransactionManager클래스는 애플리케이션 서버에 따라 다양한 후자의 객체의 위치를 자동감지하기 위해 셋팅될수 있다. TransactionManager인스턴스에 대한 접근을 가지는 것은 고급 트랜잭션 성질을 허용한다. 좀더 상세한 정보를 위해서는 JtaTransactionManager Javadocs을 보라.

9.8.1. BEA 웹로직

웹로직 7.0, 8.1 또는 그 이상의 환경에서, 당신은 JtaTransactionManager클래스 대신에 WebLogicJtaTransactionManager를 사용하는 것을 선호할것이다. 특정 웹로직은 일반적인 JtaTransactionManager의 하위클래스를 명시한다. 이것은 표준 JTA 성질(트랜잭션 명, 트랜잭션마다의 격리 레벨, 모든 경우내 트랜잭션의 재개를 포함한 기능)을 넘어서 웹로직 관리 트랜잭션 환경에서 강력한 Spring의 트랜잭션 정의의 지원한다.

9.8.2. IBM 웹스피어

웹스피어 5.1, 5.0 과 4 환경에서, 당신은 Spring의 WebSphereTransactionManagerFactoryBean클래스를 사용하고자 할것이다. 이것은 웹스피어의 정적 접근 메소드를 통해 수행하는 웹스피어 환경에서 JTA TransactionManager를 가져오는 factory bean이다. 이 메소드는 웹스피어의 각각의 버전마다 다르다. JTA TransactionManager 인스턴스가 이 factory bean을 통해 획득되었을때, JTA UserTransaction객체의 사용을 넘어 고급 트랜잭션 성질을 위해 Spring의 JtaTransactionManager는 이것에 대한 참조를 가지고 설정된다.

9.9. 공통적인 문제에 대한 해결법

9.9.1. 특정 DataSource를 위한 잘못된 트랜잭션 관리자 사용하기

개발자들은 그들의 요구사항들에 맞는 적절한 PlatformTransactionManager 구현을 사용하는데 주의를 기울여야 한다. Spring 트랜잭션 추상화가 JTA 전역 트랜잭션과 동작하는 방식을 이해하는 것은 중요한 일이다. 적절하게 사용되었을 때, 여기엔 아무런 문제가 없다. Spring 프레임워크는 단지 간소화하고 이식가능한 추상화만을 제공한다.

만약 당신이 전역 트랜잭션을 사용한다면, 당신은 모든 트랜잭션적인 동작들에 대해 Spring의 org.springframework.transaction.jta.JtaTransactionManager 클래스(또는 특정 애플리케이션 서버에 대한 하위클래스)를 반드시 사용해야만 한다. 만약 그렇지 않으면 Spring 프레임워크는 컨테이너 DataSource 인스턴스와 같은 자원들에서 로컬 트랜잭션을 수행하고자 할 것이다. 그런 로컬트랜잭션들은 말이 안되며, 좋은 애플리케이션 서버라면 그것들을 에러로 간주할 것이다.

9.10. 더 많은 자원

Spring 프레임워크의 트랜잭션 지원에 대한 더 많은 자원을 위해 아래의 링크를 보라.

  • InfoQ에서 출판된 Java트랜잭션 디자인 전략이라는 책은 좋다. Java의 트랜잭션에 대해 잘 소개했다. 이것또한 Spring 프레임워크와 EJB3모두에서 트랜잭션을 설정하고 사용하는 방법에 대한 예제를 포함한다.