우리는 스프링에서 제공하는 @Transcational을 이용해 여러 작업을 하나의 트랜잭션으로 쉽게 묶어 처리할 수 있다. 또한 스프링에서는 트랜잭션 추상화를 제공할 뿐만 아니라 프로젝트에 사용되는 데이터 접근 기술을 인식해서 적절한 구현체를 빈으로 등록해준다.
@Transcational을 사용하기 전 JDBC를 예로 들어보자. 하나의 쿼리문을 실행하기 위해서 커넥션을 획득하고, Preparedstatement를 생성하는 등 반복되는 코드가 DB에 접근하는 모든 메서드에 동일하게 작성되었다. @Transcational은 이런 문제를 한번에 해결해준다. @Transcational은 프록시를 적용해 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있게 해준다.
흐름은 다음과 같다.
그럼 트랜잭션이 적용되었는지 어떻게 확인할 수 있을까?
코드로 알아보자.
@Slf4j
@SpringBootTest
public class TxBasicTest {
// 빈 등록 등 생략
@Test
void proxyCheck() {
log.info("aop class={}", basicService.getClass());
assertThat(AopUtils.isAopProxy(basicService)).isTrue();
}
@Test
void txTest() {
basicService.tx();
basicService.nonTx();
}
static class BasicService {
@Transactional
public void tx() {
log.info("call tx");
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
public void nonTx() {
log.info("call nonTx");
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
}
logging.level.org.springframework.transaction.interceptor=TRACE
스프링은 언제나 그렇듯이 자세하고 구체적인 것이 더 높은 우선순위를 가진다. 이는 트랜잭션 적용에 있어서도 마찬가지다. 코드로 바로 확인하자.
@Slf4j
@Transactional(readOnly = true)
static class LevelService {
// Transactional의 경우 default가 readOnly=false이기 때문에
// 파라미터값은 지워도 됨
@Transactional(readOnly = false)
public void write() {
log.info("call write");
printTxInfo();
}
public void read() {
log.info("call read");
printTxInfo();
}
결과를 확인해보자.
출처 : 김영한 - 스프링 DB 2편