
// 명시적으로 코드를 작성한다면..
public class businessService {
// 트랜잭션 매니저
private final PlatformTransactionManager transactionManager;
public void basicLogic(String userId) throws SQLException {
// 트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 비즈니스 로직
bizLogic(userId);
transactionManager.commit(status); // 성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}
}
private void bizLogic(String userId) throws SQLException {
...
}
}
[Controller]
↓ (Service 프록시 호출)
[프록시 객체 (@Transactional)]
↓
[TransactionInterceptor]
↓
[PlatformTransactionManager (JpaTransactionManager)]
↓
[EntityManagerFactory → EntityManager]
↓
[EntityManager.begin()/commit()/rollback()] ← 트랜잭션 실행
=> 트랜잭션이 프록시를 통해 동작을 하기 때문에, self-invocation 문제가 발생한다.
"Spring에서 트랜잭션은 프록시를 통해 동작하기 때문에,
같은 클래스 내부에서 자기 자신의 메서드를 호출(self-invocation)하면 프록시를 거치지 않고 직접 호출하게 되고,
이로 인해 @Transactional이 적용되지 않는 문제가 발생한다."
@Service
public class PaymentService {
@Transactional
public void processPayment(Payment payment) {
savePayment(payment);
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // 별도의 새 트랜잭션을 생성
public void savePayment(Payment payment) {
}
}
서비스 클래스 분리 방식 (가장 많이 사용)
// 결제 저장을 담당하는 서비스
@Service
public class PaymentSaveService {
@Autowired
private PaymentRepository paymentRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void savePayment(Payment payment) {
// 새 트랜잭션에서 실행됨
paymentRepository.save(payment);
}
}
// 결제 처리를 담당하는 서비스
@Service
public class PaymentProcessService {
@Autowired
private PaymentSaveService paymentSaveService; // 분리된 서비스 주입
@Transactional
public void processPayment(Payment payment) {
// 비즈니스 로직 처리...
// 분리된 서비스의 메서드 호출 - 프록시를 통해 호출되므로 트랜잭션 적용됨
paymentSaveService.savePayment(payment);
// 추가 로직...
}
}
=> 여기서는 프록시 객체가 의존성 주입될 때 주입되어 트랜잭션을 관리함 -> processPayment에서 savePayment를 호출해도 프록시를 통해 호출되어 @Transactional이 적용된다.
그 외의 self-injection, AopContext 사용 방식도 있지만, 잘 사용하지 않는다.
@Service
public class PaymentService {
@Autowired
private PaymentService self; // 자기 자신의 프록시 주입
@Transactional
public void processPayment(Payment payment) {
// 프록시를 통해 호출하여 트랜잭션 적용
self.savePayment(payment);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void savePayment(Payment payment) {
// 새 트랜잭션에서 실행됨
}
}
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PaymentService {
@Transactional
public void processPayment(Payment payment) {
// 현재 프록시 객체를 가져와서 savePayment 메서드 호출
((PaymentService) AopContext.currentProxy()).savePayment(payment);
// 다른 비즈니스 로직...
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void savePayment(Payment payment) {
// 새 트랜잭션에서 실행됨
// 데이터베이스 저장 로직...
}
}
=> 이 모든것들이, AOP가 적용된 객체가 생성될 때 먼저 proxy 객체를 만들어서 실제 객체를 lazyLoading을 해주기 때문에,
proxy 객체가 존재하여 가능한 것