본문 바로가기
Spring&SpringBoot/<토비의 스프링 3.1 Vol1.1>, 이일민

TransactionInterceptor 적용하기

by 민휘 2023. 4. 20.

TransactionInerceptor는 스프링이 제공하는 트랜잭션 경계설정 어드바이스이다. 프록시로 동작하므로 타깃이 호출될 때 트랜잭션을 시작해서 부가기능을 적용한다. TransactionInerceptor는 트랜잭션 정의를 메소드 이름 패턴을 이용해서 다르게 지정할 수 있는 방법을 제공한다.

 

트랜잭션 정의

TransactionDefinition 인터페이스는 트랜잭션의 동작방식에 영향을 줄 수 있는 네 가지 속성을 정의한다.

  • 트랜잭션 전파 : 트랜잭션 실행 도중에 독자적인 트랜잭션을 가진 코드를 실행해야할 때 어떤 영향을 미치게 할 것인지 결정한다.
    • PROPAGATION_REQUIRED : 이미 진행중인 트랜잭션이 있으면 참여한다.
    • PROPAGATION_REQUIRES_NEW : 새로 시작한다.
    • PROPAGATION_NOT_SUPPORTED : 트랜잭션 없이 동작한다.
  • 격리 수준 : 트랜잭션을 고립시키는 수준을 조정한다.
  • 제한 시간 : 트랜잭션을 수행한느 제한시간을 설정한다. 트랜잭션을 직접 시작하는 전파 옵션을 사용해야 적용된다.
  • 읽기 전용 : 트랜잭션 내에서 데이터를 조작하는 시도를 막는다.

 

메소드 이름 패턴을 이용한 트랜잭션 속성 지정

TransactionInerceptor를 빈으로 등록할 때 transactionAttributes를 지정할 수 있다. key는 메소드 이름 패턴으로 주고 value에 트랜잭션 속성을 지정한다.

<bean id="transactionAdvice" class="org.springframework.transaction.interceptor.TransactionInterceptor">
	<property name="transactionManager" ref="transactionManager"/>
	<property name="transactionAttributes">
		<props>
			<prop key="get*">PROPAGATION_REQUIRED,readOnly,timeout_30</prop>
			<prop key="upgrade*">PROPAGATION_REQUIRES_NEW,ISOLATION_SERIALIZABLE</prop>
			<prop key="*">PROPAGATION_REQUIRED</prop>
		</props>
	</property>
</bean>

 

트랜잭션 속성은 다음과 같은 문자열로 정의한다.

PROPAGATION_REQUIRED,ISOLATION_NAME,readOnly,timeOUT_NNNN,-Exception1,+Exception2
  • 트랜잭션 전파 방식, 격리 수준, 읽기 전용 항목, 제한 시간
  • -예외 : 체크 예외 중 롤백 대상으로 초기화할 것 (체크는 비즈니스 예외이므로 보통 커밋한다)
  • +예외 : 런타임 예외 중 롤백하지 않을 예외 (보통 런타임은 복구 불가능하므로 롤백한다)

 

tx 네임스페이스를 사용해 더 간결하게 표현할 수 있다.

<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="get*" propagation="REQUIRED" read-only="true" timeout="30"/>
		<tx:method name="upgrade*" propagation="REQUIRES_NEW" isolation="SERIALIZABLE"/>
		<tx:method name="*" propagation="REQUIRED" />
	</tx:attributes>
</tx:advice>

 

 

트랜잭션 적용 시 주의사항

프록시 방식을 사용하기 때문에, 프록시로 대체될 수 있는 타깃 오브젝트의 참조를 통해 타깃을 호출하는 경우에만 부가기능 적용이 가능하다. 그래서 타깃 안에서 타깃을 직접 호출하는 경우에는 부가기능이 적용되지 않는다. 이 문제를 해결하려면 타깃 안에서 프록시를 직접 DI 받게 하거나 프록시 외에 바이트 코드 조작을 사용하는 AOP를 사용하면 된다.

 

TransactionInterceptor 적용하기

  1. 트랜잭션 경계를 설정할 클래스 정하기 : 주로 서비스 계층. 서비스 계층이 복잡하지 않다면 DAO로 통합하기도 한다.
  2. 포인트컷 표현식 등록
  3. 트랜잭션 어드바이스 등록
  4. 테스트
<aop:config>
	<aop:advisor advice-ref="transactionAdvice" pointcut="bean(*Service)"/>
</aop:config>

<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="get*" propagation="REQUIRED" read-only="true"/>
		<tx:method name="*" propagation="REQUIRED"/>
	</tx:attributes>
</tx:advice>

 

get*으로 시작하는 메소드에는 전파 속성, 읽기 전용 트랜잭션을 적용하고 그 외에는 읽기 전용 속성을 적용하지 않는다.

다음과 같이 get으로 시작하는 메소드는 읽기 전용 트랜잭션이 적용된다. 그래서 중간에 update를 호출해 수정 작업을 하려고 하면 예외가 발생한다.

static class TestUserService extends UserServiceImpl {
	// ..

	public List<User> getAll() {
		for(User user: super.getAll()) {
			super.update(user);
		}
		return null;
	}
}

@Test(expected = TransientDataAccessResourceException.class)
public void readOnlyTransactionAttribute(){
	TestUserService.getAll();
}