스프링은 트랜잭션을 시작(가져오기)하고, 커밋하고, 롤백하는 기능을 추상화하였습니다.
추상화 덕분에 우리는 스프링 트랜잭션을 쉽게 관리할 수 있는데요. 오늘은 스프링 트랜잭션 추상화에 대해 간단히 알아보겠습니다.
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
- 구현체에 의존이 낮아짐
필수 트랜잭션을 위해 사용하는 PlatformTransactionManager
인터페이스 덕분에 필요하다면 트랜잭션을 쉽게 모킹, 스터빙할 수 있게 됐습니다. 즉, 구현체에 직접 의존하는 정도가 매우 낮아진 것이죠.
만약 트랜잭션을 추상화한 인터페이스가 존재하지 않는다면 사용하는 data access
전략에 따라 트랜잭션을 가져오고, 커밋하고, 롤백하는 데 많은 코드의 수정이 필요하다고 생각하면 될 것 같습니다.
실제로 각 데이터 접근 기술들은 위 인터페이스를 상속받아 구현체를 생성하고 있습니다. 스프링에서 이 인터페이스를 IoC 컨테이너에 올려 의존성으로 관리하고 있어 우리는 정말 쉽게 트랜잭션을 관리할 수 있습니다.
- 언체크드 익셉션 발생
트랜잭션 내에서 발생하는 예외는 굉장히 심각한 것들이 많습니다. 하지만 위 인터페이스에서 던지는 TransactionException
은 unchecked Exception이기 때문에 개발자가 해당 예외를 catch할 것인지 handling 할 것인지 의사결정 할 수 있습니다.
- 트랜잭션 동기화
getTransaction
은 새로운 트랜잭션을 생성하여 가져오기도 하지만, 만약 적합한 트랜잭션이 현재 콜스택에 존재한다면 해당 트랜잭션을 가져오는 메서드입니다. 즉, Transaction Status
가 실행 쓰레드와 연관 있다는 뜻이죠.
스프링에서 가장 추천하는 동기화 방식은 템플릿 기반의 영속성 통합 api나 orm api 중 트랜잭션을 인식하는 팩터리 빈을 사용하는 방법이 있습니다. JDBC의 DataSourceUtils
, JPA의 EntityManagerFactoryUtils
, Hibernate의 SessionFactoryUtils
클래스를 사용하면 connection이 없을 경우 connection을 생성하여
TransactionSynchronizationManager에 등록하고, connection이 존재하는 경우 해당 connection을 가져다 사용합니다.
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
// TransactionSynchronizationManager에서 가져다 씀
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
//...
// TransactionSynchronizationManager에 등록
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
TransactionSynchronizationManager
는 내부적으로 ThreadLocal
을 사용하여 구현되어 있어 동일한 쓰레드에 대한 트랜잭션에만 접근이 가능합니다.
일반적으로 각 api의 도우미 클래스를 사용하기 때문에 내부적으로 위에서 언급한 클래스를 이용하여 connection을 가져오고 트랜잭션을 실행합니다. 그래서 우리는 아무것도 모르고 @Transactional
을 달아 트랜잭션을 사용하고 있던 거죠..
정리
정리하자면 스프링이 트랜잭션을 위한 모든 준비를 해두었기 때문에 우리는 애노테이션으로 트랜잭션을 열고 닫을 수 있습니다.
https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-strategies
https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#tx-resource-synchronization
org.springframework.jdbc.datasource.DataSourceUtils
org.springframework.transaction.support.TransactionSynchronizationManager
'우아한테크코스 4기 > 레벨2' 카테고리의 다른 글
[Spring] 스프링의 일관된 예외처리 (0) | 2022.05.12 |
---|---|
[SQL] 페이징 구현하기 (2) | 2022.05.07 |
[Spring] 컨트롤러 테스트 시 한글 깨짐 문제 해결 (0) | 2022.04.24 |
[Spring] MockMvc 를 이용한 컨트롤러 테스트 (0) | 2022.04.23 |
[Spring] NamedParameterJdbcTemplate 사용하기 (4) | 2022.04.21 |