본문으로 바로가기

[Spring] @Transactional의 이해

category Spring 2019. 11. 28. 17:07

스프링에서 트랜잭션 처리를 위해 선언적 트랜잭션을 사용한다.

선언전 트랜잭션이란 설정 파일 or 어노테이션 방식으로 간편하게 트랜잭션에 관한 행위를 정의하는 것이다.

(물론, 프로그래밍 방식으로 직접 트랜잭션을 처리할 수도 있다)

 

이글에서는 트랜잭션의 기본 개념보다 선언적 트랜잭션의 사용 방법, 동작 원리, 주의점, 사용 예시에 대해 살펴볼 것이다.

 

 

1. 사용 방법

크게 2가지 방식으로 사용된다. 최근에는 간편하게 어노테이션 기반으로 많이 사용한다.

  • XML 파일을 이용한 설정

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

 

  • 어노테이션을 이용한 설정

@Transactional을 클래스 단위 혹은 메서드 단위에 선언해주면 된다.

클래스에 선언하게 되면, 해당 클래스에 속하는 메서드에 공통적으로 적용된다. (test(), test2() 메서드에 적용)

메서드에 선언하게 되면, 해당 메서드에만 적용된다. (test() 메소드에 적용)

//클래스 단위 설정
@Service
@Transactional
public class TestService {

	public void test() {

	}
	
	public void test2() {
		
	}

}
//메소드 단위 설정
@Service
public class TestService {

	@Transactional
	public void test() {

	}

	public void test2() {

	}
}

 

 

2. 동작 원리

@Transactional 어노테이션을 기준으로 설명하겠다.

트랜잭션은 Spring AOP를 통해 구현되어있다. 

더 정확하게 말하면, 어노테이션 기반 AOP를 통해 구현되어있다. (import문을 보면 알 수 있다)

import org.springframework.transaction.annotation.Transactional;

 

따라서, 아래와 같은 특징이 있다

  • 클래스, 메소드에 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체 생성
  • 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit or Rollback을 수행
  • CheckedException or 예외가 없을 때는 Commit
  • UncheckedException이 발생하면 Rollback

 

 

3. 주의점

1) 우선순위

@Transactional은 우선순위를 가지고 있다.

클래스 메서드에 선언된 트랜잭션의 우선순위가 가장 높고, 인터페이스에 선언된 트랜잭션의 우선순위가 가장 낮다.

클래스 메소드 -> 클래스 -> 인터페이스 메소드 -> 인터페이스

따라서 공통적인 트랜잭션 규칙은 클래스에, 특별한 규칙은 메서드에 선언하는 식으로 구성할 수 있다.

 

 

또한, 인터페이스 보다는 클래스에 적용하는 것을 권고한다.

  • 인터페이스나 인터페이스의 메서드에 적용할 수 있다.
  • 하지만, 인터페이스 기반 프록시에서만 유효한 트랜잭션 설정이 된다.
  • 자바 어노테이션은 인터페이스로부터 상속되지 않기 때문에  클래스 기반 프록시 or AspectJ 기반에서 트랜잭션 설정을 인식 할 수 없다.

 

2) 트랜잭션의 모드

@Transactional은 Proxy Mode와 AspectJ Mode가 있는데 Proxy Mode가 Default로 설정되어있다.

Proxy Mode는 다음과 같은 경우 동작하지 않는다.

  • 반드시 public 메서드에 적용되어야한다.
    • Protected, Private Method에서는 선언되어도 에러가 발생하지는 않지만, 동작하지도 않는다.
    • Non-Public 메서드에 적용하고 싶으면 AspectJ Mode를 고려해야한다.
  • @Transactional이 적용되지 않은 Public Method에서 @Transactional이 적용된 Public Method를 호출할 경우, 트랜잭션이 동작하지 않는다.

 

 

4. @Transactional 설정

속성 타입 설명
value  String  사용할 트랜잭션 관리자
propagation enum: Propagation 선택적 전파 설정
isolation enum: Isolation 선택적 격리 수준
readOnly boolean 읽기/쓰기 vs 읽기 전용 트랜잭션
 timeout
int (초) 트랜잭션 타임 아웃
rollbackFor Throwable 로부터 얻을 수 있는 Class 객체 배열 롤백이 수행되어야 하는, 선택적인 예외 클래스의 배열
rollbackForClassName Throwable 로부터 얻을 수 있는 클래스 이름 배열 롤백이 수행되어야 하는, 선택적인 예외 클래스 이름의 배열
noRollbackFor Throwable 로부터 얻을 수 있는 Class 객체 배열 롤백이 수행되지 않아야 하는, 선택적인 예외 클래스의 배열
noRollbackForClassName Throwable 로부터 얻을 수 있는 클래스 이름 배열 롤백이 수행되지 않아야 하는, 선택적인 예외 클래스 이름의 배열

 

 

5. 다중 Transaction Manager

필요에 따라서 다수의 독립된 트랜잭션 매니져를 사용할 수 있다.

이는 @Transactional의 value 속성에 사용할 PlatformTransactionlManager를 지정하면 된다.

 

참고로 PlatformTransactionManager는 Spring에서 제공하는  트랜잭션 관리 인터페이스이다.

인터페이스의 구현체에는 JDBC, 하이버네이트 등이 있는데 이는 트랜잭션이 실제 작동할 환경이다.

따라서 반드시 알맞은 PlatformTransactionlManager 구현체가 정의되어  있어야한다.

 

다시 본론으로 돌아와, 다중 트랜잭션 매니저는 다음과 같이 지정한다.

value 속성에 Bean의 id or qualifier 값을 지정한다. 

아래는 qualifier를 이용한 Spring Docs의 예제이다.

public class TransactionalService {

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

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

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

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

 

 

6. 일반적인 사용 예제

트랜잭션은 다음과 같은 경우 사용한다.

  • 하나의 작업 or 여러 작업을
  • 하나의 작업 단위로 묶어 Commit or Rollback 처리가 필요할 때

특히, JPA를 사용한다면 단일 작업에 대해서는 @Transactional을 직접 선언할 필요가 없다.

JPA의 구현체를 살펴보면 모든 메서드에 이미 @Transactional이 선언되어 있기 때문에 문제가 발생하면 Rollback 처리해주기 때문이다.

 

따라서, 여러 작업을 하나의 단위로 묶어 Commit or Rollback 처리가 필요할 때 직접 선언해주면 된다.

결과를 확인하기 위해 간단한 사용 예시를 만들었다.

 

test() 메서드에서 @Transactional 선언을 통해 saveSuccess()와 saveFail()을 하나의 작업 단위로 묶었다.

saveSuccess() 메서드는 문제가 없지만, saveFail()에 문제가 생겨 saveSuccess() 역시 저장되지 않고 Rollback 된다.

 

 

만약, @Trasactional을 선언하지 않는다면 saveFail()의 실패 여부와 관계없이 saveSuccess()는 저장된다.

DB에 saveSuccess()가 저장되었다.

 


오늘의 글귀

어떤 사람에게서 빛이 나는 건 깊은 어둠을 지나온 까닭이다 - 이동경 작가

'Spring' 카테고리의 다른 글

[Spring] 싱글톤 레지스트리와 주의점  (0) 2019.09.28