Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Spring 레퍼런스] 11장 트랜잭션 관리 #2

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.



11.5.4 다른 빈에 다른 트랜잭션의 의미를 설정하기
다수의 서비스계층 객체가 있고 각각에 완전히 다른 트랜잭션 설정을 적용하고자 하는 시나리오를 생각해 보자. 이러한 경우 다른 pointcut과 advice-ref 속성값을 가진 별개의 <aop:advisor/> 요소를 정의할 수 있다.

약 간의 차이점있지만 우선 모든 서비스 계층의 클래스는 x.y.service 패키지 루트에 정의되어 있다고 가정한다. 모든 빈을 이 패키지(혹은 그 하위 팩키지)에 정의된 클래스의 인스턴스로 만들고 Service로 이름이 끝나는 모든 빈이 기본 트랜잭션 설정을 가지도록 하려면 다음과 같이 작성해야 한다.

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

  <aop:config>

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

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

  </aop:config>

  <!-- 이 두 빈은 트랜잭션이 적용될 것이다... -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>
  <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

  <!-- ... 그리고 이 두 빈은 트랜잭션이 적용되지 않는다 -->
  <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (적합한 패키지에 있지 않다) -->
  <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (이름이 'Service'로 끝나지 않는다) -->

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

  <!-- PlatformTransactionManager와 같은 다른 트랜잭션 인프라스트럭처 빈은 생략했다... -->

</beans>

다음 예제는 환전히 다른 트랜잭션 설정으로 별도의 두 빈을 설정하는 방법을 보여준다.

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

  <aop:config>

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

    <aop:pointcut id="noTxServiceOperation"
          expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

    <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

    <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

  </aop:config>

  <!-- 이 빈은 트랜잭션이 적용된다 ('defaultServiceOperation' 포인트컷을 참고해라) -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- 이 빈도 트랜잭션이 적용되지만 완전히 다른 트랜잭션 설정을 가진다 -->
  <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>

  <!-- PlatformTransactionManager와 같은 다른 트랜잭션 인프라스트럭처 빈은 생략했다... -->
</beans>


11.5.5 <tx:advice/> 설정
이번 색션에서는 <tx:advice/> 태그를 사용해서 지정할 수 있는 여러가지 트랜잭션 설정을 간략히 설명한다. 기본 <tx:advice/> 설정은 다음과 같다.

  • 전파(Propagation) 설정은 REQUIRED다.
  • 격리 수준(Isolation level)은 DEFAULT이다.
  • 트랜잭션은 읽기/쓰기이다.
  • 트랜잭션 타입아웃 기본값은 의존하는 트랜잭션 시스템의 기본 타입아웃이거나 타임아웃을 지원하지 않는다면 존재하지 않는다.
  • 모든 RuntimeException은 롤백을 발생시키고 모든 체크드 Exception은 롤백을 발생시키지 않는다.
이 기본 설정을 변경할 수 있다. <tx:advice/>와 <tx:attributes/>내에 중첩된 <tx:method/> 태그의 여러가지 속성은 아래에 정리되어 있다.

Table 11.1. <tx:method/> 설정
속성 필수여부? 기본값 설명
name Yes   연결된 트랜잭션 속성의 메서드 이름. 와일드카드 (*) 문자를 다수의 메서드를 가진 같은 트랜잭션 속성 설정과 연결하는데 사용할 수 있다. 예를 들어, get*, handle*, on*Event 등등 이다.
propagation No REQUIRED 트랜잭션 전파 동작.
isolation No DEFAULT 트랜잭션 격리 수준.
timeout No -1 트랜잭션 타임아웃 값 (초단위).
read-only No false 해당 트랜잭션이 읽기 전용인가?
rollback-for No   롤백을 일으키는 Exception(s). 콤마로 구분한다. 예를 들면 com.foo.MyBusinessException,ServletException.
no-rollback-for No   롤백을 일으키지 않는 Exception(s). 콤마로 구분한다. 예를 들어 com.foo.MyBusinessException,ServletException.



















11.5.6 @Transactional 사용하기
트 랜잭션 설정에 대한 XML에 기반한 선언적인 접근에 추가적으로 어노테이션 기반의 접근을 사용할 수 있다. 자바 소스코드에 직접 트랜잭션을 선언하는 것은 영향받는 코드에 훨씬 가깝게 선언을 둘 수 있다. 어쨌든 트랜잭션을 사용하는 코드는 거의 항상 이러한 방법으로 배포되기 때문에 과도한 커플링으로 인한 큰 위험은 없다.

@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);
}

위의 POJO가 스프링 IoC 컨테이너의 빈처럼 정의되었을 때 딱 한 줄의 XML 설정을 추가해서 빈 인스턴스에 트랜잭션을 적용할 수 있다.

<!-- '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-3.0.xsd
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
  
  <!-- 트랜잭션을 적용하고자 하는 서비스 객체다 -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- 어노테이션에 기반한 트랜잭션 동작의 설정을 활성화한다. -->
  <tx:annotation-driven transaction-manager="txManager"/>

  <!-- a PlatformTransactionManager는 여전히 필요하다 -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!-- (이 의존성은 어딘가에 정의되어 있다) -->
  <property name="dataSource" ref="dataSource"/>
  </bean>
  
  <!-- 다른 <bean/> 정의는 여기에 한다 -->
</beans>

Tip
연 결하려는 PlatformTransactionManager의 빈 이름이 transactionManager인 경우에는 <tx:annotation-driven/> 태그에서 transaction-manager 속성을 생략할 수 있다. 의존성 주입하려는 PlatformTransactionManager 빈이 다른 이름을 가지고 있다면 앞의 예제처럼 transaction-manager 속성을 명시적으로 사용해야 한다.

인터페이스 정의, 인터페이스의 메서드, 클래스 정의, 클래스의 퍼블릭 메서드 앞에 @Transactional 어노테이션을 둘 수 있다. 하지만 단지 @Transactional 어노테이션의 존재만으로는 트랜잭션 동작을 활성화하기에 충분하지 않다. @Transactional 어노테이션은 단순히 @Transactional을 인지하는 몇몇 런타임 인프라스트럭처가 소비할 수 있는 메타데이터이고 적절한 빈에 트랜잭션 동작을 설정하는데 메타데이터를 사용할 수 있다. 앞의 예제에서 <tx:annotation-driven/> 요소가 트랜잭션 동작을 활성화한다.

메서드 가시성과 @Transactional
프 록시를 사용할 때는 public 가시성을 가진 메서드에만 @Transactional 어노테이션을 적용해야 한다. protected나 private, package-visible 메서드에 @Transactional 어노테이션을 붙히면 오류가 발생하지는 않지만 어노테이션이 붙은 메서드는 설정된 트랜잭션 설정에 나타나지 않는다. 퍼블릭이 아닌 메서드에 어노테이션을 붙혀야 한다면 AspectJ의 사용을 고려해봐라(아래 참고).

Tip
스 프링은 인터페이스에 어노테이션을 붙히는 것과는 반대로 구현(concrete) 클래스(그리고 구현 클래스의 메서드들)에만 @Transactional 어노테이션을 붙히기를 권장한다. 확실히 인터페이스(또는 인터페이스의 메서드)에도 @Transactional 어노테이션을 붙힐 수 있지만 인터페이스 기반의 프록시를 사용하는 경우에만 제대로 동작한다. 자바의 어노테이션이 인터페이스를 상속받지 않는다는 점은 클래스 기반의 프록시 (proxy-target-class="true")를 사용하거나 위빙기반의 관점 (mode="aspectj")을 사용할 때 프록시와 위빙 인프라스트럭처가 트랜잭션 설정을 인지하지 못하고 해당 객체가 트랜잭션이 적용된 프록시로 감싸지지 않는다는 것(아주 안좋다)을 의미한다.

Note
프 록시 모드에서(기본값이다) 프록시를 통한 외부 메서드 호출만을 가로챈다. 즉, 호출된 메서드가 @Transactional로 표시되어 있더라도 자기호출(self-invocation, 대상 객체의 다른 메서드를 호출하는 대상 객체내의 메서드)은 런타임시에 실제로 트랜잭션이 되지 않을 것이다.

자기호출이 트랜잭션으로 잘 감싸지길 원한다면 AspectJ 모드의 사용을 고려해 봐라.(아래 표의 mode 속성을 참고해라.) 우선 이 경우에 프록시가 없다. 대신, 모든 종류의 메서드에서 @Transactional을 런타임동작으로 바꾸기 위해 대상 객체는 위빙된 것이다.(즉 대상객체의 바이트코드가 수정될 것이다.)


Table 11.2. <tx:annotation-driven/> 설정
속성 기본값 설명
transaction-manager transactionManager 사용할 트랜잭션 관리자의 이름. 위의 예제처럼 트랜잭션 관리자의 이름이 transactionManager이 아닌 경우에만 필요하다.
mode proxy 기 본 모드인 "proxy"가 스프링의 AOP 프레임워크를 사용해서 프록시되는 어노테이션이 붙은 빈을 처리한다.(위에서 얘기한 다음의 프록시 의미는 프록시를 통한 메서드 호출에만 적용된다.) 다른 모드인 "aspectj"는 스프링의 AspectJ 트랜잭션 관점으로 영향받은 클래스를 대신 위빙해서 모든 종류의 메서드 호출에 적용하기 위해 대상객체의 바이트코드를 수정한다. AspectJ 위빙은 활성화된 로드타임 위빙(또는 컴파일타임 위빙)과 마찬가지로 클래스패스에 spring-aspects.jar를 필요로 한다.(로드타임 위빙을 설정하는 방법은 Section 8.8.4.5, “스프링 설정”를 참고해라.)
proxy-target-class false proxy 모드에만 적용된다. @Transactional 어노테이션이 붙은 클래스에 어떤 타입의 트랜잭션 프록시를 생성할 것인지 제어한다. proxy-target-class 속성을 true로 설정했다면 클래스기반의 프록시가 생성된다. proxy-target-class가 false이거나 이 속성을 생략하면 표준 JDK 인터페이스 기반 프록시가 생성된다. (다른 프록시 타입의 자세한 설명은 Section 8.6, “프록싱 메카니즘”를 참고해라.)
order Ordered.LOWEST_PRECEDENCE @Transactional 어노테이션이 붙은 빈에 적용되는 트랜잭션 어드바이스의 순서를 정의한다. (AOP 어드바이스의 순서와 관계된 규칙에 대한 내용은 Section 8.2.4.7, “어드바이스 순서”를 참고해라.) 순서를 지정하지 않으면 AOP 서브시스템이 어드바이스의 순서를 결정한다.


































Note
@Transactional 어노테이션이 붙은 클래스에 어떤 타입의 트랜잭션이 적용된 프록시를 생성할 것인 지를 <tx:annotation-driven/> 요소의 proxy-target-class 속성이 제어한다. proxy-target-class 속성을 true로 설정했으면 클래스기반의 프록시가 생성된다. proxy-target-class가 false이거나 이 속성을 생략하면 표준 JDK 인터페이스기반의 프록시가 생성된다. (다른 프록시 타입에 대한 내용은 Section 8.6, “프록싱 메카니즘”를 참고해라.)

Note
<tx:annotation- driven/>는 같은 어플리케이션 컨텍스트에 정의된 @Transactional가 붙은 빈만을 찾는다. 즉, DispatcherServlet을 위해서 WebApplicationContext에 <tx:annotation-driven/>를 두면 서비스가 아니라 컨트롤러에서만 @Transactional 빈을 확인한다. 자세한 내용은 Section 16.2, “The DispatcherServlet”를 참고해라.

메 서드에 트랜잭션 설정을 평가할 때 가장 깊은 위치(most derived location)의 설정을 우선시한다. 다음 예제에서 DefaultFooService 클래스는 읽기전용 트랜잭션 설정으로 클래스수준에 어노테이션을 붙혔지만 같은 클래스의 updateFoo(Foo) 메서드의 @Transactional 어노테이션이 클래스수준에 정의된 트랜잭션 설정보다 앞선다.

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

  public Foo getFoo(String fooName) {
    // 어떤 작업을 한다
  }

  // 이 메서드에는 이 설정이 우선시된다
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // 어떤 작업을 한다
  }
}


11.5.6.1 @Transactional 설정
@Transactional 어노테이션은 인터페이스, 클래스, 메서드가 트랜잭션이 되어야 한다는 것을 지정하는 메타데이터이다. 예를 들어 “해당 메서드가 호출될 때 기존의 존재하는 트랜잭션은 중지시키고 읽기 전용의 새로운 트랜잭션을 시작해야 한다는 식이다.” 기본 @Transactional 설정은 다음과 같다.

  • 전파 설정은 PROPAGATION_REQUIRED 이다.
  • 격리 수준은 ISOLATION_DEFAULT 이다.
  • 트랜잭션은 읽기/쓰기가 가능하다.
  • 트랜잭션 타임아웃의 기본값은 의존하는 트랜잭션 시스템의 기본 타임아웃값이거나 타임아웃이 지원되지 않는다면 타임아웃이 없다.
  • 모든 RuntimeException은 롤백을 실행하고 체크드 Exception은 롤백하지 않는다.
이 기본설정은 변경할 수 있다. @Transactional 어노테이션의 다양한 프로퍼티는 다음 표에 정리해 놨다.


Table 11.3. @Transactional 프로퍼티
프로퍼티 타입 설명
value String 사용할 트랜잭션 관리자를 지정하는 선택적인 제한자(qualifier)
propagation enum: Propagation 선택적인 전파 설정.
isolation enum: Isolation 선택적인 격리수준.
readOnly boolean 읽기/쓰기 트랜잭션인가? 읽기전용 트랜잭션인가?
timeout int (in seconds granularity) 트랜잭션 타임아웃.
rollbackFor Throwable에서 얻어져야하는 Class 객체들의 배열. 반드시 롤백해야 하는 예외 클래스의 선택적인 배열.
rollbackForClassname 클래스 이름의 배열. Throwable에서 얻어져야 하는 클래스들. 반드시 롤백해야 하는 예외 클래스 이름의 선택적인 배열.
noRollbackFor Throwable에서 얻어져야 하는 Class 객체들의 배열. 반드시 롤백하지 않아야 하는 예외 클래스의 선택적인 배열.
noRollbackForClassname Throwable에서 얻어져야 하는 클래스 이름 String의 배열. 반드시 롤백하지 않아야 하는 예외 클래스 이름의 선택적인 배열.





















지 금은 트랜잭션의 이름으로 명시적인 제어를 할 수 없다. 여기서 '이름'은 가능한 경우 트랜잭션 모니터(예를 들면 웹로직의 트랜잭션 모니터)와 로깅 출력에 나올 트랜잭션의 이름을 의미한다. 선언적인 트랜잭션에서 트랜잭션 이름은 항상 정규화된 클래스명 + "." + 트랜잭션하게 어드바이즈된 클래스의 메서드명이다. 예를 들어 BusinessService 클래스의 handlePayment(..) 메서드가 트랜잭션을 시작하면 트랜잭션의 이름은 com.foo.BusinessService.handlePayment가 된다.


11.5.6.2 @Transactional과 여러 트랜잭션 관리자
대 부분의 스프링 어플리케이션은 딱 하나의 트랜잭션 관리자만 필요로 하지만 하나의 어플리케이션에서 여러 개의 독립적인 트랜잭션 관리자가 필요한 상황이 있을 것이다. @Transactional 어노테이션의 value 속성은 사용할 PlatformTransactionManager의 식별자를 선택적으로 지정하는데 사용할 수 있다. 이는 트랜잭션 관리자 빈의 이름이나 제한자(qualifier) 값이 될 수 있다. 예를 들어 제한자(qualifier) 표기법을 사용한 다음의 자바코드는

public class TransactionalService {
  
  @Transactional("order")
  public void setSomething(String name) { ... }

  @Transactional("account")
  public void doSomething() { ... }
}

어플리케이션 컨텍스트에서 다음의 트랜잭션 관리자 빈 선언과 섞을 수 있다.

<tx:annotation-driven/>

  <bean id="transactionManager1" class="org.springframework.jdbc.DataSourceTransactionManager">
    ...
    <qualifier value="order"/>
  </bean>

  <bean id="transactionManager2" class="org.springframework.jdbc.DataSourceTransactionManager">
    ...
    <qualifier value="account"/>
</bean> 

이 경우에 TransactionalService의 두 메서드는 "order"와 "account" 제한자로 구분된 별도의 트랜잭션 관리자에서 실행될 것이다. 적합한 PlatformTransactionManager 빈을 특별히 찾아내지 못한다면 transactionManager 빈 이름을 가리키는 기본 <tx:annotation-driven>를 여전히 사용할 것이다.


11.5.6.3 커스텀 단축 어노테이션
여러 메서드의 @Transactional에 같은 속성을 반복적으로 사용하고 있다면 스프링 메타어노테이션 지원을 이용해서 커스텀 단축 어노테이션을 정의할 수 있다. 예를 들면 다음과 같은 어노테이션을 정의해서

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}  

다음과 같이 이전 섹션의 예제를 작성할 수 있다.

public class TransactionalService {
  
  @OrderTx
  public void setSomething(String name) { ... }

  @AccountTx
  public void doSomething() { ... }
}

여기서는 트랜잭션 관리자 제한자를 정의하는 문법을 사용했지만 전파 동작, 롤백 규칙, 타임아웃 등도 사용할 수 있다.


11.5.7 트랜잭션 전파
이번 섹션에서는 스프링에서 트랜잭션 전파의 의미를 설명한다. 이번 섹션이 트랜잭션 전파의 적절한 소개가 아니라는 것을 유념해야 한다. 오히려 스프링에서 트랜잭션 전파에 관련된 몇 가지 의미를 설명한다.

스프링이 관리하는 트랜잭션에서sms 물리적인 트랜잭션과 논리적인 트랜잭션간의 차이점과 이 차이점에 전파설정을 적용하는 방법을 알아야 한다.


11.5.7.1 Required
PROPAGATION_REQUIRED

PROPAGATION_REQUIRED

전파 설정이 PROPAGATION_REQUIRED인 경우 설정이 적용된 각 메서드마다 논리적인 트랜잭션의 범위가 생성된다. 이러한 각각의 논리적인 트랜잭션 범위는 외부 트랜잭션 범위는 내부 트랜잭션 범위와 논리적으로 독립되어 개별적인 롤백전용(rollback-only) 상태를 결정한다. 물론 표준 PROPAGATION_REQUIRED 동작의 경우 이러한 모든 범위는 같은 물리적인 트랜잭션에 매핑될 것이다. 그래서 내부 트랜잭션 범위에 설정된 롤백전용 표시(marker)는 외부 트랜잭션이 실제로 커밋할 기회에 영향을 준다.(기대대로)

하지만 내부 트랜잭션 범위가 롤백전용으로 설정된 경우에 외부 트랜잭션 스스로 롤백을 결정하지 않으므로 롤백을(내부 트랜잭션 범위가 조용히 실행한다.) 예상하지 못한다. 이 때 대응되는 UnexpectedRollbackException이 던져진다. 이는 트랜잭션의 호출자가 커밋되지 않아야 하는 경우 커밋되었다고 가정하지 않도록 할 수 있는 예상된 동작이다. 그래서 내부 트랜잭션(외부 호출자가 알지 못하는)은 트랜잭션을 조용히 롤백전용으로 표시하고 외부 호출자는 여전히 커밋을 호출한다. 외부 호출자는 커밋이 아니라 롤백이 수행되었다는 것을 명확히 알도록 UnexpectedRollbackException를 받야야 한다.


11.5.7.2 RequiresNew
PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRED 와는 반대로 PROPAGATION_REQUIRES_NEW는 영향받은 각각의 트랜잭션 범위에 완전히 독릭적인 트랜잭션을 사용한다. 이러한 경우 의존하는 물리적인 트랜잭션이 다르므로 외부 트랜잭션이 내부 트랜잭션의 롤백상태에 영향을 받지 않고 독립적으로 커밋하거나 롤백할 수 있다.


11.5.7.3 Nested
PROPAGATION_NESTED 는 롤백할 수 있는 여러 세이브포인트(savepoint)를 가진 하나의 물리적인 트랜잭션을 사용한다. 이러한 부분 롤백으로 어떤 작업이 롤백되었더라도 외부 트랜잭션은 계속해서 물리적인 트랜잭션을 진행하면서 내부 트랜잭션 범위가 자신의 범위에서 롤백을 실행할 수 있게 한다. 이 설정은 보통 JDBC 세이브포인트에 매핑되므로 JDBC 리소스 트랜잭션에서만 동작할 것이다. 스프링의 DataSourceTransactionManager를 참고해라.


11.5.8 트랜잭션 작업 어드바이징하기
트랜잭션 작업과 몇가지 기본적인 프로파일링 어드바이스를 둘 다 실행한다고 생각해보자. <tx:annotation-driven/> 컨텍스트에서 이렇게 하려면 어떻게 해야하는가?

updateFoo(Foo) 메서드를 호출할 때 다음의 동작을 보기 원할 것이다.

  1. 설정된 프로파일링 관점 시작.
  2. 트랙잭션이 적용된 어드바이스 실행.
  3. 어드바이즈된 객체의 메서드 실행.
  4. 트랜잭션 커밋.
  5. 프로파일링 관점이 전체 트랜잭션 메서드 호출의 정확한 실행시간을 리포팅함.
Note
이번 장에서는 AOP의 자세한 내용은 설명하지 않는다.(트랜잭션을 적용하는 것과 관련된 것은 제외하고) 일반적인 다음의 AOP와 AOP의 설정에 대한 자세한 내용은 Chapter 8, 스프링의 관점 지향 프로그래밍를 참고해라.

다음은 앞에서 얘기한 간단한 프로파일링 관점에 대한 코드이다. 어드바이스의 순서는 Ordered 인터페이스로 제어한다. 어드바이스 순서에 대한 자세한 내용은 Section 8.2.4.7, “어드바이스 순서”를 참고해라.

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;

  // 어드바이스의 순서를 제어할 수 있게 한다
  public int getOrder() {
    return this.order;
  }

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

  // 이 메서드는 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-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

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

  <!-- 관점이다 -->
  <bean id="profiler" class="x.y.SimpleProfiler">
    <!-- 트랜잭션이 적용된 어드바이스 이전에 실행한다 (그러므로 순서(order) 번호가 작다) -->
    <property name="order" value="1"/>
  </bean>

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

  <aop:config>
    <!-- 이 어드바시스는 트랜잭션이 적용된 어드바이스 주위에서(around) 실행될 것이다 -->
    <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 빈에 프로파일링과 트랜잭셔널 관점을 적용한다. 유사한 방법으로 다수의 추가 관점을 설정한다.

다음 예제는 위의 설정과 같은 효과가 있지만 순수하게 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-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

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

  <!-- 프로파일링 어드바이스 -->
  <bean id="profiler" class="x.y.SimpleProfiler">
    <!-- 트랜잭션이 적용된 어드바이스 이전에 실행된다. (그러므로 순서(order) 번호가 낮다) -->
    <property name="order" value="1"/>
  </bean>

  <aop:config>

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

    <!-- 프로파일링 어드바이스 이후에 실행될 것이다. (order 속성을 비교해봐라) -->
    <aop:advisor
        advice-ref="txAdvice"
        pointcut-ref="entryPointMethod"
        order="2"/> <!-- order 값이 프로파일링 관점보다 크다 -->

    <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>

  <!-- DataSource와 PlatformTransactionManager같은 다른 <bean/> 정의는 여기에 한다 -->
</beans>

앞 의 설정으로 순서에 따라 프로파일링 관점과 트랜잭셔널 관점이 fooService 빈에 적용될 것이다. 트랜잭셔널 어드바이스에 진입한 이후와 트랜잭셔널 어드바이스가 끝나기 전에 프로파일링 어드바이스를 실행하려고 하면 그냥 프로파일링 관점 빈의 order 프로퍼티 값을 트랜잭셔널 어드바이스의 order 값보다 높게 바꿔주면 된다.

추가적인 관점도 비슷한 방법으로 설정한다.


11.5.9 AspectJ로 @Transactional 사용하기
스 프링 컨테이너 외부에서 AspectJ 관점으로 스프링 프레임워크의 @Transactional 지원을 사용할 수도 있다. 이렇게 하려면 일단 클래스(선택적으로 클래스의 메서드에)에 @Transactional 어노테이션을 붙히고 그 다음 spring-aspects.jar에 정의된 org.springframework.transaction.aspectj.AnnotationTransactionAspect와 어플리케이션은 연결(위브, weave)한다. 관점도 반드시 트랜잭션 관리자로 설정해야 한다. 당연히 관점을 의존성 주입하는데 스프링 프레임워크의 IoC 컨테이너를 사용할 수 있다. 트랜잭션 관리 관점을 설정하는 가장 간단한 방법은 <tx:annotation-driven/> 요소를 사용하고 Section 11.5.6, “@Transactional 사용하기”에서 설명한 것처럼 aspectj에 mode 속성을 지정하는 것이다. 여기서는 스프링 컨테이너 외부에서 동작하는 어플리케이션에 대해서 얘기하고 있으므로 프로그래밍적으로 설정하는 방법을 설명할 것이다.

Note
계속 읽기 전에 Section 11.5.6, “@Transactional 사용하기”와 Chapter 8, 스프링의 관점 지향 프로그래밍를 읽으면 도움이 될 것이다.

// 적절한 트랜잭션 관리자를 생성한다 
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// 사용할 AnnotationTransactionAspect를 설정한다. 이는 반드시 트랜잭션이 적용된 어떤 메서드라도 실행하기 전에 이뤄져야 한다.
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager); 

Note
이 관점을 사용할 때는 클래스가 구현한 인터페이스(존재한다면)가 아니라 구현 클래스(또는 구현클래스의 메서드)에 어노테이션을 붙혀야 한다. AspectJ는 인터페이스에 붙은 어노테이션은 상속받지 않는다는 자바의 규칙을 따른다.

클래스에 붙은 @Transactional 어노테이션은 클래스의 모든 메서드 실행에 대한 기본 트랜잭션 동작을 지정한다.

클래스내의 메서드에 붙은 @Transactional 어노테이션은 클래스 어노테이션(존재한다면)이 지정한 기본 트랜잭션 동작을 덮어쓴다. 가시성에 상관없이 모든 메서드는 어노테이션이 붙을 수 있다.

AnnotationTransactionAspect으로 어플리케이션을 위빙하려면 어플리케이션은 AspectJ로 구성하거나(AspectJ 개발 가이드 참고) 로드타입 위빙을 사용해야 한다. AspectJ를 사용하는 로드타임 위빙에 대한 내용은 Section 8.8.4, “스프링 프레임워크에서 AspectJ를 사용한 로드타임 위빙(Load-time weaving)”를 참고해라.


11.6 프로그래밍적인 트랜잭션 관리
스프링 프레임워크는 프로그래밍적인 트랜잭션 관리의 두가지 수단을 제공한다.

  • TransactionTemplate 사용하기.
  • PlatformTransactionManager 구현체를 직접 사용하기.
스프링 팀은 프로그래밍적인 트랜잭션 관리에 보통 TransactionTemplate를 추천한다. 두번째 접근은 예외 처리에 대한 부단이 적기는 하지만 JTA UserTransaction API를 사용하는 것과 유사하다.


11.6.1 TransactionTemplate 사용하기
TransactionTemplate 은 JdbcTemplate같은 다른 스프링 템플릿과 같은 접근을 채택했다. TransactionTemplate는 어플리케이션 코드가 보일러플레이트 획득과 트랜잭셔널 리소스의 해지에서 자유롭도록 콜백 접근을 사용한다. 그래서 코드는 의도지향적(intention driven)이 되고 작성한 코드는 개발자가 하고자 한것에만 오로지 집중할 수 있다.

Note
다 음에 예제에서 보듯이 TransactionTemplate를 사용하면 스프링의 트랜잭션 인프라스트럭처 API에 완전히 커플링된다. 개발요구사항에 프로그래밍적인 트랜잭션 관리가 적합하든지 적합하지 않든지간에 결정은 스스로 하는 것이다.

트 랜잭션 컨텍스트에서 실행되어야 하고 명시적으로 TransactionTemplate를 사용할 어플리케이션 코드는 다음과 같다. 어플리케이션 개발자는 트랜잭션 컨텍스트에서 실행해야하는 코드를 담고 있는 TransactionCallback 구현체를 작성한다. 그 다음 작성한 커스텀 TransactionCallback의 인스턴스를 TransactionTemplate에 노출된 execute(..) 메서드에 전달한다.

public class SimpleService implements Service {

  // 이 인스턴스의 모든 메서드에서 공유되는 하나의 TransactionTemplate
  private final TransactionTemplate transactionTemplate;

  // PlatformTransactionManager를 제공하기 위해 생성자 주입을 사용한다
  public SimpleService(PlatformTransactionManager transactionManager) {
    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
    this.transactionTemplate = new TransactionTemplate(transactionManager);
  }

  public Object someServiceMethod() {
    return transactionTemplate.execute(new TransactionCallback() {

      // 이 메서드의 코드는 트랜잭션 컨텍스트에서 실행된다
      public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
      }
    });
  }
}

반환값이 없으면 다음과 같이 익명 클래스로 간편한 TransactionCallbackWithoutResult 클래스를 사용해라.

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

  protected void doInTransactionWithoutResult(TransactionStatus status) {
    updateOperation1();
    updateOperation2();
  }
});

콜백내의 코드는 제공된 TransactionStatus 객체의 setRollbackOnly() 메서드를 호출해서 트랜잭션을 롤백할 수 있다.

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

  protected void doInTransactionWithoutResult(TransactionStatus status) {
    try {
      updateOperation1();
      updateOperation2();
    } catch (SomeBusinessExeption ex) {
      status.setRollbackOnly();
    }
  }
});


11.6.1.1 트랜잭션 설정 지정하기
프 로그래밍적으로나 설정에서 TransactionTemplate에 전파모드, 격리수준, 타임아웃 등의 트랜잭션 설정을 지정할 수 있다. 기본적으로 TransactionTemplate 인스턴스는 기본 트랜잭션 설정을 가진다. 다음 예제는 특정 TransactionTemplate의 트랜잭션 설정을 프로그래밍적으로 커스터마이징한 것을 보여준다.

public class SimpleService implements Service {

  private final TransactionTemplate transactionTemplate;

  public SimpleService(PlatformTransactionManager transactionManager) {
    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
    this.transactionTemplate = new TransactionTemplate(transactionManager);

    // 원한다면 트랜잭션 설정은 여기서 명시적으로 설정할 수 있다
    this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    this.transactionTemplate.setTimeout(30); // 30 초
    // 등등...
  }
}

다음 예제는 스프링 XML 설정을 사용해서 몇가지 커스텀 트랜잭션 설정으로 TransactionTemplate를 정의한다. sharedTransactionTemplate를 필요한 만큼의 서비스에 주입할 수 있다.

<bean id="sharedTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
  <property name="timeout" value="30"/>
</bean>"

마 지막으로 TransactionTemplate 클래스의 인스턴스들은 스레드세이프하므로 어떤 대화식(conversational) 상태도 유지하지 않는다. 하지만 TransactionTemplate 인스턴스는 설정(configuration) 상태를 유지한다. 그러므로 다수의 클래스가 하나의 TransactionTemplate 인스턴스를 공유할 것이므로 클래스가 다른 설정(예를 들어 다른 격리수준)의 TransactionTemplate를 사용해야 한다면 두 가지의 다른 TransactionTemplate 인스턴스를 생성해야 한다.


11.6.2  PlatformTransactionManager 사용하기
트 랜잭션을 관리하는데 org.springframework.transaction.PlatformTransactionManager를 직접 사용할 수도 있다. 단순히 사용하는 PlatformTransactionManager의 구현체를 빈 레퍼런스로 빈에 전달해라. 그 다음 TransactionDefinition와 TransactionStatus를 사용해서 트랜잭션을 시작하고 롤백하고 커밋할 수 있다.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 트랜잭션 이름만이 프로그래밍적으로 명시적으로 설정할 수 있다
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
  // 여기의 비즈니스 로직을 실행한다
}
catch (MyException ex) {
  txManager.rollback(status);
  throw ex;
}
txManager.commit(status);


11.7 프로그래밍적인 트랜잭션 관리와 선언적인 트랜잭션 중에서 선택하기
프 로그래밍적인 트랜잭션 관리는 적은 수의 트랜잭션 작업이 있을 때만 보통 좋은 생각이다. 예를 들어 특정 업데이트 작업에만 트랜잭션이 필요한 웹어플리케이션인 경우 스프링이나 다른 기술을 사용해서 트랜잭션이 적용된 프록시를 설정하기 원치 않을 것이다. 이러한 경우 TransactionTemplate을 사용하는 것이 좋은 접근이 될 수 있다. 명시적으로 트랜잭션 이름을 설정할 수 있는 것도 트랜잭션 관리에 프로그래밍적인 접근을 사용해서만 할 수 있는 것이다.

반면에 어플리케이션에 매우 많은 트랜잭션 작업이 있다면 선언적인 트랜잭션 관리가 일반적으로 휼륭하다. 선언적인 트랜잭션 관리는 비즈니스 로직 외부에서 트랜잭션 관리를 하고 설정하기가 어렵지 않다. EJB CMT가 아니라 스프링 프레임워크를 사용할 때 선언적인 트랜잭션 관리의 설정 비용은 엄청나게 줄어든다.


11.8 어플리케이션 서버에 특화된 통합
스 프링의 트랜잭션 추상화는 보통 어플리케이션 서버와 관계가 없다. 게다가 JTA UserTransaction와 TransactionManager 객체들을 검색하는 JNDI를 선택적으로 수행하는 스프링의 JtaTransactionManager 클래스는 어플리케이션 서버마다 다른 TransactionManager의 위치를 자동으로 탐지한다. JTA TransactionManager로의 접근해서 특히 트랜잭션 중지 지원같은 향상된 트랜잭션을 사용할 수 있다. 자세한 내용은 JtaTransactionManager Javadoc을 참고해라.

스프링의 JtaTransactionManager는 Java EE 어플리케이션 서버에서 실행할 때의 일반적인 선택이고 별도의 설정을 하지 않고도 일반적인 모든 서버에서 동작한다고 알려져있다. 트랜잭션 중지같은 향샹된 기능은 다수의 서버에서 잘 동작한다.(GlassFish, JBoss, Geronimo, Oracle OC4J를 포함해서) 하지만 완전한 트랜잭션 중지 지원과 더 향상된 통합을 위해서 스프링은 IBM WebSphere, BEA WebLogic 서버, Oracle OC4J에 대한 전용 아답터를 제공한다. 이러한 아답터들은 다음 섹션에서 설명한다.

WebLogic 서버, WebSphere, OC4J를 포함한 일반적인 시나리오에서는 간편한 <tx:jta-transaction-manager/> 설정요소의 사용을 고려해 봐라. 이 요소를 설정하면 의존하는 서버를 자동으로 탐지하고 플랫폼에서 사용할 수 있는 가장 좋은 트랜잭션 관리자를 선택한다. 즉, 서버에 특화된 아답터 클래스(앞의 섹션에서 설명했듯이)를 명시적으로 설정하지 않아도 된다. 반대로 아답터 클래스들은 자동으로 선택하고 기본 폴백(fallback)으로 표준 JtaTransactionManager를 사용한다.


11.8.1 IBM WebSphere
WebSphere 6.1.0.9 이상의 버전에서 사용하길 추천하는 스프링 JTA 트랜잭션 관리자는 WebSphereUowTransactionManager이다. 이 전용 아답터는 웹스피어 어플리케이션 서버 6.0.2.19 이상의 버전과 6.1.0.9 이상의 버전에서 사용할 수 있는 IBM의 UOWManager API를 사용한다. 이 아답터를 사용하면 스프링 주도의 트랜잭션 중지(PROPAGATION_REQUIRES_NEW으로 시작된 것처럼 중지/복귀)를 IBM이 공식적으로 지원한다!


11.8.2 BEA WebLogic 서버
웹 로직 서버 9.0 이상에서는 JtaTransactionManager 클래스 대신 WebLogicJtaTransactionManager를 보통 사용할 것이다. 이 클래스는 웹로직에 특화된 클래스로 일반적인 JtaTransactionManager의 하위클래스로 표준 JTA 의미를 넘어 웹로직이 관리하는 트랜잭션 환경에서 스프링의 트랜잭션 정의를 완전히 지원한다. 트랜잭션 이름, 트랜잭션당 격리 수준, 모든 경우에서 트랜잭션을 적절히 복귀하는 등의 기능을 포함한다.


11.8.3 Oracle OC4J
스프링은 OC4J 10.1.3 이상의 버전을 위해서 전용 아답터 클래스인 OC4JJtaTransactionManager를 제공한다. 이 클래스는 앞에서 설명한 WebLogicJtaTransactionManager 클래스와 유사해서 OC4J에 트랜잭션 이름, 트랜잭션당 격리 수준같은 부가가치를 제공한다.

트랜잭션 중지를 포함한 전체 JTA 기능은 OC4J상에서도 스프링의 JtaTransactionManager로 잘 동작한다. 전용 OC4JJtaTransactionManager 아답터는 표준 JTA 이상의 부가가치를 제공할 뿐이다.


11.9 일반적인 문제에 대한 해결책

11.9.1 지정한 DataSource에 잘못된 트랜잭션 관리자의 사용
트 랜잭션 기술과 요구사항의 선택에 기반해서 올바른 PlatformTransactionManager 구현체를 사용해라. 적절히 사용하면 스프링 프레임워크는 단지 직관적이고 이식성있는 추상화를 제공할 뿐이다. 전역 트랜잭션을 사용한다면 모든 트랜잭션 작업에 org.springframework.transaction.jta.JtaTransactionManager 클래스(또는 이 클래스의 하위클래스이면서 어플리케이션에 특화된 클래스)를 반드시 사용해야 한다. 그렇지 않으면 트랜잭션 인프라스트럭처가 컨테이너 DataSource 인스턴스같은 리소스에 지역 트랜잭션을 수행하려고 한다. 이러한 지역 트랜잭션은 적합하지 않으며 좋은 어플리케이션 서버라면 에러로 취급한다.


11.10 관련 자료
스프링 프레임워크의 트랜잭션 지원에 대한 더 자세한 정보는 다음을 참고해라.

  • XA를 사용하든지 안하든간에 스프링에서 분산 트랜잭션은 스프링소스의 David Syer가 스프링 어플리케이션에서 분산 트랜잭션의 7가지 패턴을 설명하는 JavaWorld의 발표자료이다. 7가지 패턴 중 3가지는 XA를 사용하고 4가지는 사용하지 않는다.
  • 자바 트랜잭션 디자인 전략InfoQ의 책으로 자바에서 트랜잭션에 대한 좋은 소개를 제공한다. 그리고 이 책은 스프링 프레임워크와 EJB3 모두에서 트랜잭션을 설정하고 사용하는 방법에 대한 예제들도 포함되어 있다.

2012/11/28 03:17 2012/11/28 03:17