@Transactional은 Spring AOP로 동작한다. Spring AOP는 프록시 패턴을 기반으로 동작하게 되는데 프록시 객체를 자동 생성하는 방법으로 JdkDynamicProxy, CGLIB을 사용한다.
@Transactional이 붙은 메서드, 클래스가 호출이 되면, 자동으로 생성된 프록시에서 트랜잭션을 열고 호출이 종료되면 트랜잭션을 닫는다. 또한, 예외가 발생하면 롤백까지 시켜준다.
(그림 출처 : https://www.slideshare.net/rohitsghatol/aspect-oriented-prog-with-aspectj-spring-aop)
public class Proxy extends TargetClass {
TargetClass target;
@Override
public void doSomething() {
transaction_begin();
target.doSomething();
transaction_end();
}
}
위와 같은 코드가 Spring AOP를 통해 생성된다(이해를 돕기 위한 예시이다.).
@Service
public class TxService {
public void callSomething() {
String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
System.out.println("callSomething : " + currentTransactionName);
doSomething();
}
@Transactional
public void doSomething() {
String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
System.out.println("doSomething : " + currentTransactionName);
}
}
------테스트 코드-------
@SpringBootTest
class TxTest {
@Autowired
TxService txService;
void test() {
txService.callSomething();
}
}
위 코드는 트랜잭션이 동작할까???
callSomething : null
doSomething : null
현재 트랜잭션의 이름을 출력한 결과, 동작하지 않는다.
왜 ???
callSomething()은 @Transactional이 붙지 않은 메서드이기 때문에, TxService 인스턴스의 메서드가 호출된다. 그리고 내부에서 doSomething() 메서드를 호출하게 되는데, 자바에서는 메서드 앞에 아무것도 붙어 있지 않으면 this를 붙여 호출한다. 따라서 프록시 객체를 타지 않고 바로 TxService 인스턴스의 메서드가 호출되기 때문에 트랜잭션이 동작하지 않는 것이다.
@Service
public class TxService {
@Autowired
TxServiceCalled txServiceCalled;
public void callSomething() {
String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
System.out.println("callSomething : " + currentTransactionName);
txServiceCalled.doSomething();
}
}
----------------------------------------------------
@Service
public class TxServiceCalled {
@Transactional
public void doSomething() {
String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
System.out.println("doSomething : " + currentTransactionName);
}
}
-----------테스트 결과-------------
callSomething : null
doSomething : com.example.community.service.TxServiceCalled.doSomething
위 코드처럼 doSomething()이 다른 Bean에 존재한다면 트랜잭션이 동작한다. TxService의 callSomething() 내부에서 다른 Bean의 메서드를 호출하므로 프록시 객체가 만들어져 트랜잭션을 타게 된다.
주의사항
@Transactional이 붙은 메서드를 @Transactional이 붙어있지 않은 같은 클래스의 메서드에서 호출하게 되면, 프록시를 거치지 않아 트랜잭션을 타지 않는다.참고자료