본문 바로가기

프로그래밍(TA, AA)/JVM 언어

[스프링] 트랜잭션의 종류

1. 로컬 트랜잭션


2. 분산 트랜잭션(글로벌 트랜잭션)

2개 또는 그 이상의 네트워크에 연결된 호스트 간의 트랜잭션

XA: 오픈그룹(The Open Group)에서 제정한 분산 트랜잭션 처리를 위한 표준


https://en.wikipedia.org/wiki/Distributed_transaction

https://en.wikipedia.org/wiki/X/Open_XA




선언적인 트랜잭션 관리


- 대부분의 스프링 프레임워크의 사용자들은 선언적인 트랜잭션 관리를 선택합니다.

- 선언적인 트랜잭션 관리는 애플리케이션에 최소한으로 영향을 주므로 비침투적인 경량 컨테이너의 이상과 매우 일치합니다.

- 스프링 프레임워크의 선언적인 트랜잭션 관리는 스프링의 관점지향 프로그래밍(AOP)와 사용가능하도록 만들어졌습니다.

- 롤백 규칙의 개념은 중요합니다. 롤백 규칙은 예외(그리고 throwable)이 자동으로 롤백을 수행해야한다고 지정할 수 있게 합니다. 롤백 규칙은 자바 코드가 아니라 설정파일에 선언적으로 지정합니다.

- EJB 컨테이너의 기본 동작은 시스템 예외(보통 런타임 예외)에 자동으로 트랜잭션을 롤백하는 것이더라도 EJB CMT는 애플리케이션 예외에는 자동으로 트랜잭션을 롤백하지 않습니다. (즉, java.rmi.RemoteException를 제외한 체크드 익셉션)

- 선언적인 트랜잭션 관리의 스프링 기본동작이 EJB 관례(unchecked Exception에만 자동으로 롤백함)을 따르지만 스프링의 기본동작을 커스터마이징하는 것은 종종 유용합니다.



스프링 프레임워크의 선언적인 트랜잭션 관리와 EJB CMT(Container Managed Transaction)의 차이


- JTA와 묶인 EJB CMT와는 다르게 스프링 프레임워크의 선언적인 트랜잭션 관리는 어떤 환경에서도 동작합니다. 선언적인 트랜잭션 관리는 단순히 설정파일을 조정함으로써 JTA 트랜잭션이나 JDBC, JPA, 하이버네이트, JDO를 사용하는 로컬 트랜잭션과 동작할 수 있습니다.

- 스프링 프레임워크의 선언적인 트랜잭션 관리를 EJB같은 전용 클래스만이 아니라 어떤 클래스에도 적용할 수 있습니다.

- 스프링 프레임워크는 EJB에는 없는 선언적인 롤백 규칙 기능을 제공합니다. 프로그래밍적인 지원과 선언적인 지원 모두 롤백 규칙을 제공합니다.

- 스프링 프레임워크는 AOP를 사용해서 트랜잭션 동작을 커스터마이징할 수 있게 해줍니다. 예를 들어 트랜잭션을 롤백하는 경우 커스텀 동작을 추가할 수 있습니다. 트랜잭션이 적용된 어드바이스 사이에 임의의 어드바이스를 추가할 수도 있습니다. EJB CMT에서는 setRollbackOnly()를 제외하고는 컨테이너의 트랜잭션 관리에 영향을 줄 수 없습니다.

- 스프링 프레임워크는 하이엔드 애플리케이션 서버가 지원하는 원격 호출에 걸친 트랜잭션 컨텍스트의 전파를 지원하지 않습니다. 이 기능이 필요하다면 EJB를 사용하기를 추천합니다. 하지만 보통은 원격 호출에 걸친 트랜잭션을 원하지 않으므로 이러한 기능을 사용하기 전에 신중하게 고려해보아야 합니다.



선언적인 트랜잭션 구현체의 이해


- 스프링 프레임워크의 선언적인 트랜잭션 지원을 이해하는 가장 중요한 개념은 트랜잭션이 AOP 프록시를 통해서 활성화되고 메타데이터(현재는 XML기반이거나 어노테이션 기반)로 트랜잭션이 적용된 어드바이스가 유도(driven)된다는 점입니다.

- 트랜잭션이 적용된 메타데이터와 AOP의 조합은 메서드 호출 주위에 트랜잭션을 유도하기 위해 적절한 PlatformTransactionManager 구현체와 결합한 TransactionInterceptor를 사용하는 AOP 프록시를 만듭니다.





전파방식(propagation)


전파방식은 트랜잭션이 언제 생성되는지, 언제 현재의 트랜잭션에 결부되야 하는지를 정의합니다. (기본값: REQUIRED)


 전파방식

 설명

 기존 트랜잭션이 있음

 기존 트랜잭션이 없음

 REQUIRED

 트랜잭션 상황에서 실행됨

 참여

 신규

 REQUIRED_NEW

 자신만의 트랜잭션 상황에서 실행됨

 신규

 신규

 SUPPORTS

 트랜잭션이 없더라도 실행 가능함

 참여

 X

 NOT_SUPPORTED

 트랜잭션이 없는 상황에서 실행 가능함

 X

 X

 MANDATORY

 트랜잭션이 반드시 존재해야 함

 참여

 예외발생

 NEVER

 트랜잭션이 반드시 없어야 함

 예외 발생

 X

 NESTED

 중첩된 트랜잭션에서 실행

 기존 트랜잭션과는 독립적으로 커밋이나 롤백 가능

 벤더 의존적이며 지원이 안되는 경우도 많음

 중첩

 REQUIRED와 동일



격리 수준(isolation level)


더티 읽기(dirty read)

- 한 트랜잭션에서 다른 트랜잭션에 의해 변경되었지만 아직 커밋되지 않은 데이터를 읽게 되는 문제

- 이 데이터가 커밋되지 않고 롤백되어 버린다면, 첫번째 트랜잭션에서 읽은 이 데이터는 유효하지 않은 데이터가 됨


반복할 수 없는 읽기(nonrepeatable read)

- 트랜잭션이 같은 질의를 두 번 이상 수행할 때 서로 다른 데이터를 얻게 되는 문제

- 보통 각 질의 수행 사이에 동시 진행 중인 다른 트랜잭션에서 이 데이터를 변경하는 경우에 발생함.


팬텀 읽기(phantom read)

- 어떤 트랜잭션(T1)이 둘 이상의 데이터 행을 읽은 다음, 동시 진행 중인 다른 트랜잭션(T2)이 추가 행을 삽입할 때 발생함.

- T1에서 동일한 질의를 다시 수행하면, T1은 이전에 없던 데이터 행까지 읽음.


격리 수준은 어떤 트랜잭션에 동시 진행하는 다른 트랜잭션이 영향을 미치는 정도를 결정합니다. (기본값 : DEFAULT)


 격리 수준

 의미

 더티   읽기

 반복할 수 없는 읽기

 팬텀   읽기

 ISOLATION_DEFAULT

 하부 데이터 저장소의 기본 격리 수준 이용함
 데이터 저장소마다 다름

 -

 -

 -

 ISOLATION_READ_UNCOMMITTED

 커밋되지 않은 데이터 변경사항을 읽을 수 있음

 O

 O

 O

 ISOLATION_READ_COMMITTED

 커밋된 데이터 변경사항만 읽을 수 있음

 X

 O

 O

 ISOLATION_REPREATABLE_READ

 같은 데이터 필드는 여러번 반복해서 읽더라도 동일한 값을 읽도록 함.

 자신이 변경한 필드는 변경된 값을 읽게 됨

 X

 X

 O

 ISOLATION_SERIALIZABLE

 완전한 ACID를 보장하는 격리 수준

 가장 성능이 비효율적인 격리 수준임

 X

 X

 X



DBMS별 기본값으로 많은 DBMS가 Read Committed를 기본 트랜잭션 격리성 수준으로 채택합니다.


 DBMS

 Isolation level 기본 값

 Oracle

 READ COMMITTED

 MySql

 REPEATABLE READ (Inno DB)

 Mssql

 READ COMMITTED

 Cubrid

 REPEATABLE READ CLASS with READ UNCOMMITTED INSTANCES
 (READ UNCOMMITTED)



읽기 전용 힌트(read-only hints)


- 데이터 저장소는 트랜잭션이 시작할 때 읽기 전용 최적화를 수행합니다.

- 하이버네이트의 경우 트랜잭션을 읽기 전용으로 선언하는 것은 하이버네이트의 플러시(flush) 모드를 FLUSH_NEVER로 만들어서 하이버네이트로 하여금 객체와 데이터베이스의 불필요한 동기화를 피하고 모든 데이터 갱신을 트랜잭션 말미로 미루게합니다.

- 트랜잭션을 시작할 수 있는 전파 방식을 가진 메소드에 선언해야 의미가 있습니다. (PROPAGATION_REQUIRED / PROPAGATION_REQUIRES_NEW / PROPAGATION_NESTED)

- 기본 값: 읽기 / 쓰기



타임아웃(Timeout)


- 트랜잭션을 시작할 수 있는 전파 방식을 가진 메소드에 선언해야 의미가 있습니다. (PROPAGATION_REQUIRED / PROPAGATION_REQUIRES_NEW / PROPAGATION_NESTED)

- 기본 값: 의존하는 트랜잭션 시스템의 기본 타임아웃이거나 타임아웃을 지원하지 않는다면 존재하지 않음.



롤백 규칙(rollback rules)


- 스프링 프레임워크의 트랜잭션 인프라스트럭처가 트랜잭션 작업을 롤백하도록 추천하는 방법은 트랜잭션 컨텍스트에서 현재 실행되고 있는 코드가 Exception를 던지도록 하는 것입니다. 스프링 프레임워크의 트랜잭션 인프라스트럭처 코드는 버블링되는 호출스택처럼 다루지 않는 모든 Exception을 잡아서 트랜잭션을 롤백으로 표시할 것인지를 결정합니다.


- 기본 설정에서 스프링 프레임워크의 트랜잭션 인프라스터럭처는 런타임 익셉션과 언체크드 익셉션만 트랜잭션을 롤백으로 표시합니다. 즉, RuntimeException의 인스턴스나 하위클래스의 예외를 던졌을때 뿐입니다. (기본적으로 Error도 롤백될 것) 트랜잭션이 적용된 메서드의 체크드 익셉션은 기본설정에서는 롤백하지 않습니다.


- 체크드 익셉션을 포함해서 어떤 타입의 Exception이 트랜잭션을 롤백으로 표시하도록 할 것인지 설정할 수 있습니다.


- 가능한 모든 경우에 롤백에 대해서 선언적인 접근을 하는 것을 강력히 권장합니다. 프로그래밍적인 롤백은 절대적으로 필요한 경우에만 사용해야 하지만 프로그래밍적인 롤백을 사용하면 깔끔한 POJO 기반의 아키텍처를 버리게 됩니다.

@Transactional
public class defaultFooService implements FooService {
	...
}

<beans>
	<tx:annotation-driven/>
</beans>
<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>


생각해 볼 문제


트랜잭션 관리 방식 선택


- 프로그래밍적인 트랜잭션 관리는 적은 수의 트랜잭션 작업이 있을 때만 보통 좋은 생각입니다.

- 예를 들어 특정 업데이트 작업에만 트랜잭션이 필요한 웹 애플리케이션인 경우 스프링이나 다른 기술을 사용해서 트랜잭션이 적용된 프록시를 설정하기 원치 않을 것입니다.

- 이러한 경우 TransactionTemplate을 사용하는 것이 좋은 접근이 될 수 있습니다. 명시적으로 트랜잭션 이름을 설정할 수 있는 것도 트랜잭션 관리에 프로그램이적인 접근을 사용해서만 할 수 있는 것입니다.


- 반면에 애플리케이션에 매우 많은 트랜잭션 작업이 있다면 선언적인 트랜잭션 관리가 일반적으로 훌륭합니다.

- 선언적인 트랜잭션 관리는 비즈니스 로직 외부에서 트랜잭션 관리를 하고 설정하기가 어렵지 않습니다.

- EJB CMT가 아니라 스프링 프레임워크를 사용할때 선언적인 트랜잭션 관리의 설정 비용은 엄청나게 줄어듭니다.



지정한 DataSource에 잘못된 트랜잭션 관리자의 사용


- 트랜잭션 기술과 요구사항의 선택에 기반해서 올바른 PlatformTransactionManager 구현체를 사용해야합니다. 적절히 사용하면 스프링 프레임워크는 단지 직관적이고 이식성이?ㅆ는 추상화를 제공할 뿐입니다. 글로벌 트랜잭션을 사용한다면 모든 트랜잭션 작업에 org.springframework.transaction.jta.JtaTransactionManager 클래스(또는 이 클래스의 하위클래스이면서 애플리케이션에 특화된 클래스)를 반드시 사용해야 합니다.


- 그렇지 않으면 트랜잭션 인프라스트럭처가 컨테이너 DataSource 인스턴스 가은 리소스에 로컬 트랜잭션을 수행하려고 합니다. 이러한 로컬 트랜잭션은 적합하지 않으며 좋은 애플리케이션 서버라면 에러로 취급합니다.



관련 자료


- XA를 사용하든지 안하든간에 스프링에서 분산 트랜잭션은 스프링소스의 David Syer가 스프링 애플리케이션에서 분산 트랜잭션의 7가지 패턴을 설명하는 JavaWorld의 반표자료입니다. 7가지 패턴 중 3가지는 XA를 사용하고 4가지는 사용하지 않습니다.


- 자바 트랜잭션 디자인 전략은 InfoQ의 책으로 자바에서 트랜잭션에 대한 좋은 소개를 제공합니다. 그리고 이 책은 스프링 프레임워크와 EJB3 모두에서 트랜잭션을 설정하고 사용하는 방법에 대한 예제들도 포함되어 있습니다.