@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
이 붙어있지 않은 같은 클래스의 메서드에서 호출하게 되면, 프록시를 거치지 않아 트랜잭션을 타지 않는다.참고자료