-
추상화된 트랜잭션
트랜잭션 AOP의 가장 큰 장점은 트랜잭션과 비즈니스 코드가 분리된다는 것에 있다.
역할과 기능에 따라 코드를 나눠놓으면 코드 가독성과 유지보수성이 좋아질 수밖에 없다.
실수할 여지가 줄어들기 때문에 결과적으로 생산성에 좋다.
스프링이 제공하는 트랜잭션 AOP는 추상화가 되어 있으므로
데이터 접근기술이 달라졌을 때, 트랜잭션과 관련한 코드를 직접 수정할 필요 없이
구현체만 바꾸면 된다.
@Transactional 애노테이션을 이용해 트랜잭션을 편하게 적용할 수 있는데
이때 적용되는 방법이 프록시다. 스프링은 트랜잭션을 적용할 클래스의 객체를 상속받아 프록시를 생성한다.
해당 애노테이션을 클래스에 붙이든 메서드에 붙이든 어디에 붙어 있기만 하면 해당 객체의 프록시를 생성한다.
트랜잭션은 하나의 커넥션 단위로 발생한다.
하나의 작업 단위의 안정성을 보장받기 위해 적용하는 기술이다. 트랜잭션을 적용했을 때 일어나는 과정은 다음과 같다.
1. 프록시 호출
2. 스프링 컨테이너의 트랜잭션 매니저 획득
3. 트랜잭션 획득
4. 커넥션은 데이터소스를 통해 획득되며 해당 커넥션은 트랜잭션 동기화 매니저에 보관
- 정확히는 TLS에 커넥션이 저장되기 때문에 현재 스레드는 계속 같은 커넥션을 사용한다
5. 실제 서비스가 호출(메서드 호출)되고 데이터 접근 로직에서 커넥션을 얻는다(트랜잭션 동기화 매니저로부터)
꼭 기억해야 할 부분은 시작이 프록시 호출이라는 점이다.
프록시는 트랜잭션을 적용할 클래스를 상속받아 생성한다.
타입이 원래의 클래스이므로, 프록시는 스프링 빈에 등록된 후에
비즈니스 로직(메서드)를 호출할 수 있다.
잠재적인 위험성
트랜잭션이 적용되지 않은 메소드에서 트랜잭션을 적용한 메소드를 호출하는 상황을 생각해보자.
class Service { public void external() { log.info("call external"); printTxInfo(); internal(); } @Transactional public void internal() { log.info("call internal"); printTxInfo(); } private void printTxInfo() { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}", txActive); } } // Test service.external();
external()을 호출하면 @Transactional이 붙어 있지 않기 때문에 트랜잭션이 시작되지 않는다. 프록시에서 실제 external 메서드를 호출하는데, 실제 external에서 호출하는 internal 메서드의 this는 프록시가 아니라 Service를 가리킨다. 그러므로 트랜잭션이 적용되지 않게 된다. 적용해야 한다면 다른 방법이 필요하다.
실무에 바로 적용할 수 있는 간단하고 쉬운 방법은 트랜잭션을 적용해야 햐는 메서드를 새로운 클래스로 새로 정의해 사용하는 것이다.
@Slf4j @RequiredArgsConstructor static class CallService { private final InternalService internalService; public void external() { log.info("call external"); printTxInfo(); internalService.internal(); } private void printTxInfo() { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}", txActive); } } @Slf4j static class InternalService { @Transactional public void internal() { log.info("call internal"); printTxInfo(); } private void printTxInfo() { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); log.info("tx active={}", txActive); } }
'개발 > Spring' 카테고리의 다른 글
[Spring security5] 인증/인가에 대한 간략한 흐름 (0) 2023.12.16 내부 트랜잭션의 롤백과 외부 트랜잭션의 커밋 (0) 2023.07.06 @PostConstruct 와 @Transactional (0) 2023.07.05 @Transactional 적용 레벨 (0) 2023.07.05 application.properties의 유용한 설정 (0) 2023.06.26