< Go Back

[SPRING] 스프링 프레임워크 트랜잭션 처리

Spring

스프링 프레임워크에서는 트랜잭션 처리를 쓰레드에 동기화하고 추상화하기 위한 인터페이스를 제공한다.

PlatformTransactionManager는 내부적으로 트랜잭션 동기화 매니저를 이용해 쓰레드에 커넥션을 동기화하고, 이 동기화된 커넥션을 활용해서 트랜잭션을 처리한다. 덕분에 비즈니스 로직에서는 직접 커넥션을 이용해 커밋, 롤백을 할 필요 없이 추상화된 트랜잭션 매니저 인터페이스에 의존하여 트랜잭션을 수행할 수 있다.

트랜잭션 동기화를 담당하는 컴포넌트는 다음과 같다. org.springframework.transaction.support.TransactionSynchronizationManager

리포지토리 컴포넌트에서는 동기화된 커넥션을 획득하기 위해 DataSourceUtils를 이용한다. DataSourceUtils는 특정 데이터소스에 대해 쓰레드에 동기화된 커넥션이 있을 경우 해당 커넥션을 반환하고, 없다면 새로 생성하여 반환한다. 쓰레드에 커넥션을 동기화하기 위해 내부적으로 ThreadLocal을 사용한다.

spring-transaction-image

커넥션 획득과 반환

// 커넥션 획득
Connection con = DataSourceUtils.getConnection(dataSource);

// 커넥션 반환
DataSourceUtils.releaseConnection(con, dataSource);

트랜잭션 수행

//트랜잭션 메니저 생성
PlatformTransactionManager transactionManager = new PlatformTransactionManager(dataSource);

//트랜잭션 시작  
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

try {  
	//비즈니스 로직
	bizLogic(fromId, toId, money); 
	transactionManager.commit(status); //성공시 커밋
} catch (Exception e) { 
	transactionManager.rollback(status); //실패시 롤백
	throw new IllegalStateException(e);
}

하지만 트랜잭션 매니저를 이용하더라도 여전히 중복해서 사용해야 하는 코드가 너무 많다. 트랜잭션을 시작하고, 커밋하고, 롤백하는 코드를 작성할 필요 없이 비즈니스 로직만 작성할 수 있다면 매우 편할 것이다.

이것을 위해 스프링에서는 @Transactional 어노테이션을 제공한다. 비즈니스 로직 메소드에 이 어노테이션을 붙이기만 하면 [[Spring AOP]]에서 자동으로 트랜잭션을 수행하는 프록시를 만들어 트랜잭션을 수행해준다.

// 트랜잭션이 알아서 수행된다.
@Transactional
public void bizLogic() {
	...logic
}

@Transactional 어노테이션은 메소드 뿐만 아니라 클래스에도 추가할 수 있다. 이 경우 해당 클래스의 모든 퍼블릭 메소드들에 트랜잭션이 적용된다.

@Transactional
public class Repository {
	public save(); // transaction applied
	private saveInner(); // transaction not applied
}

트랜잭션 수행을 위해 트랜잭션 매니저가 스프링 컨테이너에 빈으로 등록되어 있어야 한다. 하지만 스프링 부트에서는 기본적으로 트랜잭션 매니저 및 데이터소스를 자동으로 등록해주므로 크게 신경쓸 일은 없다.

데이터소스는 HikariCP를 이용한다. 데이터소스 설정은 application.properties에 다음과 같이 설정을 추가해주면 된다.

application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

선언적 트랜잭션과 내부 트랜잭션 호출 문제

Spring AOP를 이용해 트래잭션 프록시를 사용하는 경우에 트랜잭션 메소드를 내부적으로 호출하게 되면 프록시가 적용되지 않은 원래의 메소드가 호출되어 트랜잭션이 적용되지 않는다.

이런 경우는 내부적으로 사용하는 메소드들을 따로 분리하여 사용함으로써 문제를 해결할 수 있다.

트랜잭션 전파

하나의 트랜잭션이 진행중인데 또다른 트랜잭션이 새로 시작된다면 새로운 트랜잭션은 원래의 트랜잭션에 참여하게 된다. 따라서 새로운 트랜잭션이 생성되지 않는다. 이 경우에는 두 트랜잭션이 모두 커밋해야 전체가 커밋된다.

만약 내부 트랜잭션이 롤백되었는데 외부 트랜잭션이 커밋을 시도한다면 UnexpectedRollbackException 예외가 발생하며 트랜잭션이 롤백된다.

만약 기존의 트랜잭션이 존재할 때 참여하지 않고 새 트랜잭션을 만들도록 하고 싶다면 전파 속성을 REQUIRES_NEW로 설정해주면 된다.