9.5.7. Advising transactional operations

트랜잭션 어드바이스과 일반 어드바이스를 모두 적용하고 싶을 때 Order 인터페이스를 사용하여 순서를 지정할 수 있습니다.

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

트랜잭션 처리를 어노테이션을 사용했기 때문에 XML 설정만 보고서는 순서를 예측할 수가 없습니다. 전부 XML로 설정한 예제를 보겠습니다.

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

이번에는 트랜잭션 어드바이저의 order가 2 라는 것이 눈에 들어옵니다. 프로파일링 어스펙트의 order가 1이기 때문에 프로파일링 어드바이스가 먼저 적용이 될 것입니다.

의문이 드는건 왜 하나는 어드바이저 단위에 order를 설정하고 다른 하나는 어스펙트 단위에 order를 설정하였느냐 인데 트랜잭션 어드바이저 말고 커스텀 어드바이스들을 기본적으로 먼저 실행하겠다는 의도가 아닐까 생각해 봅니다.

@Transactional 속성들

사용자 삽입 이미지아직 name 속성은 없습니다. name 속성을 사용해서 로깅이나 트랜잭션 모니터링을 할 때 트랜잭션 이름을 원하는 이름으로 출력할 수 있을 겁니다. 하지만 지금은 “패키지 경로가 붙은 클래스 이름.해당 메소드”가 기본 이름이 됩니다.

Property

Type

Description

propagation

enum: Propagation

optional propagation setting

isolation

enum: Isolation

optional isolation level

readOnly

boolean

read/write vs. read-only
transaction

timeout

int (초 단위)

the transaction timeout

rollbackFor

Throwable 타입의 Class 객체의 배열

발생했을 때 반드시 롤백 해야 하는 예외 클래스들

rollbackForClassname

Throwable 타입의 Class 이름의 배열

발생했을 때 반드시 롤백 해야 하는 예외 클래스들의 이름

noRollbackFor

Throwable 타입의 Class 객체의 배열

발생했을 때 롤백 하지 않아도 되는 예외 클래스들.

noRollbackForClassname

Throwable 타입의 Class 이름의 배열

발생했을 때 롤백 하지 않아도 되는 예외 클래스들의 이름

9.5.6. Using @Transactional

이전에 봤던 XML 설정을 사용하여 트랜잭션을 선언하는 방법 말고 어노테이션을 사용하여 설정하는 방법을 설명합니다.

@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

@Tracsactional 어노테이션은 인터페이스 선언, 클래스 선언, public 메소드 선언 위에 붙일 수 있습니다. 그리고 XML 설정파일에는 다음의 한 줄만 추가하면 건드릴 것이 없습니다.

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

물론 txManager는 등록되어 있어야겠죠.[footnote]이전에 살펴봤지만 transactionManager라는 이름으로 등록되어 있다면 굳이 명시적으로 tracsaction-manger 라는 속성을 사용하지 않도 알아서 찾게 됩니다.[/footnote]

이 어노테이션을 인터페이스에도 붙일 수 있다고 했지만 그건 인터페이스 기반의 프록시만 사용하겠다는 가정하에 이루어지기 때문에 모든 클래스에 붙이는 것을 권장합니다.

이유는? – The fact that annotations are not inherited means that if you are using class-based proxies then the transaction settings will not be recognised by the class-based proxying infrastructure and the object will not be wrapped in a transactional proxy (which would be decidedly bad).

이렇다고 합니다. 사실 뭔 말인지 잘 몰라서 걍 붙여 논건데요. 나중에 이해가 되면 한글로 바꾸겠습니다. Anyway 결론은 ‘트랜잭션 처리가 되어야 할 모든 클래스에 @Transactional을 붙여라.’ 입니다.

<tx:annotation-driven/> 의 속성들

Attribute

Required?

Default

Description

transaction-manager

No

transactionManager

●Transaction Manager bean의 이름

proxy-target-class

No

 

생성할 프록시의 종류를 나타냅니다.

●true 일 때는 CGLib을 사용한 클래스
기반의 프록시를 만들고 false또는 지정하지 않을  때는 JDK의 프록시를 사용합니다.

order

No

 

트랜잭션
어드바이스가 적용될 순서를 지정합니다.

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

이렇게 클래스에도 선언하고 메소드에도 선언했을 때에는 메소드에 선언한 설정의 우선순위가 더 높습니다. 따라서 위의 경우 getFoo()는 readOnly 로 처리 하고 updateFoo는 매번 새로운 트랜잭션을 생성해서 처리하며 readOnly 로 처리 되지 않습니다.

Isolation levels

여러 트랜잭션들이 동시 다발적으로 데이타에 접근하여 작업을 할 때 다음의 문제들이 발생할 수 있습니다.

Dirty read: 어떤 트랜잭션 T1이 다른 트랜잭션 T2가 아직 커밋하지 않은 데이타를 읽었는데 T2가 롤백 되는 경우. T1이 읽은 데이타는 dirty.

Nonrepeatable read: 어떤 트랜잭션 T1이 계속 해서 같은 쿼리를 날리는데 그 사이에 다른 트랜잭션 T2가 데이타를 수정하면 T1은 의아해 합니다. 대체 왜 값이 바뀐거지?

Phantom read: 어떤 트랜잭션 T1이 계속 또 쿼리를 날리는데 그 사이 다른 트랜잭션 T2가 새로운 데이타를 추가합니다. T1은 갑자기 나타난 데이타를 보고 귀신을 본 것 처럼 깜짝 놀랍니다.

Lock이라는 것을 레코드 단위로 걸어버리면 저런 문제들이 전혀 발생하지 않겠지만 성능 문제가 심각하게 되겠죠. 따라서 다음과 같은 Isolation level 이 존재합니다.

Isolation level

What it means

ISOLATION_DEFAULT

DB가 사용하는 isolation level을 따릅니다.

ISOLATION_READ_UNCOMMITTED

아직 커밋되지 않은 트랜잭션에 의해 변경된 데이터를 읽을 수
있습니다.

따라서 위에서 지적한 세 개의 문제가 모두 발생할 수 있습니다.

ISOLATION_READ_COMMITTED

●커밋 된 데이터만 읽을 수 있습니다.

Dirty read는 방지할 수
있지만 나머진 발생할 수 있습니다.

ISOLATION_REPEATABLE_READ

●현재 트랜잭션에 의해 데이터를 수정하지 않았다면 항상 같은 데이터를 읽게
합니다.

Dirty readNonrepeatable read는 방지할 수 있지만 Phantom read
방지할 수 없습니다.

ISOLATION_SERIALIZABLE

●모든 문제를 다 방지할 수 있지만 성능이 가장 좋지 않습니다..

Propagation behavior

Spring In Action을 보니 다음과 같이 적혀 있습니다.

Propagation behavior defines the boundaries of the transaction with respect to
the client and to the method being called.

클라이언트(?)와 메소드가 실행되는 트랜잭션 경계를 정의하는 속성인 듯 합니다. 7 가지 전달 방식이 있습니다.

Propagation Behavior

What it means

PROPAGATION_MANDATORY

반드시 트랜잭션 안에서 실행되어야 합니다.

현재 진행중인 트랜잭션이 없다면 예외가 발생합니다.

PROPAGATION_NESTED

현재 진행중인 트랜잭션이 존재 한다면 그 트랜잭션 내부의 트랜잭션을
생성하여 그 안에서 실행합니다.

내부 트랜잭션은 자기를 감싸고 있는 외부 트랜잭션에 독립적입니다(외부 트랜잭션이 커밋 or 롤백 되든 말든 난 나대로 커밋 or 롤백 하겠다.)

만약에 외부 트랜잭션이 없다면 PROPAGATION_REQUIRED로 지정한 것과 동일하게 수행 됩니다.

이 기능을 지원하지 않는 벤더도 있기 때문에 문서를 참조 하시기
바랍니다

PROPAGATION_NEVER

반드시 트랜잭션 처리 없이 실행되어야 합니다.

●현재 진행중인 트랜잭션이 있다면 예외가 발생합니다.

PROPAGATION_NOT_SUPPORTED

반드시 트랜잭션 처리 없이 실행되어야 합니다.

●현재 진행중인 트랜잭션이 있다면 해당 메소드 실행이 끝날 때까지 트랜잭션을
일시 정지합니다.

PROPAGATION_REQUIRED

반드시 트랜잭션 안에서 실행되어야 합니다.

●현재 진행중인 트랜잭션이 있다면 그것을 사용하고 없다면 새로운 트랜잭션을
시작합니다.

PROPAGATION_REQUIRES_NEW

반드시 자신만을 위한 트랜잭션 만들고 그 안에서 실행되어야 합니다.

●현재 진행중인 트랜잭션이 있다면 해당 메소드 실행이 끝날 때까지 트랜잭션을
일시 정지 합니다.

PROPAGATION_SUPPORTS

●반드시 트랜잭션 안에서 처리해야 할 필요는 없습니다.

●진행중인 트랜잭션이 있다면 트랜잭션 안에서 처리하고 없으면 트랜잭션 없이 처리.

참조 : Spring 워크북, Spring In Action