반응형
📌 @Transactional
이란?
Spring에서 제공하는 트랜잭션 관리 어노테이션으로, 데이터 작업의 ACID 특성을 보장하며 트랜잭션 시작, 커밋, 롤백을 자동으로 처리합니다.
💡 @Transactional의 동작 원리
@Transactional
은 AOP(Aspect-Oriented Programming) 기반으로 동작하며, 프록시 객체가 트랜잭션의 시작과 종료를 제어합니다.
프록시 동작 구조
- 클라이언트가 호출 → 프록시 객체가 메서드를 가로챔.
- 프록시 객체가 트랜잭션을 시작(Commit/Rollback 결정).
- 실제 객체의 메서드 실행.
- 메서드 종료 후 트랜잭션 종료.
📌 트랜잭션은 원래 하나인가? 독립적으로 동작한다는 의미는?
트랜잭션은 하나의 작업 단위
트랜잭션은 데이터를 다루는 논리적 작업 단위입니다. 이는 작업이 완벽히 성공하거나, 실패 시 원래 상태로 복원되는 것을 의미합니다.
독립적이라는 의미
독립적 트랜잭션은:
- 서로 다른 트랜잭션이 상호 간섭하지 않는 것을 뜻합니다.
- 전파 속성(Propagation)을 통해 다른 트랜잭션과의 관계를 정의.
📌 트랜잭션 생성 및 독립적 관리 방식
Spring의 트랜잭션 생성 방식
- 전파 속성(Propagation)과 프록시 객체를 통해 트랜잭션 생성과 관리를 결정.
- 트랜잭션 범위 내에서 데이터 작업을 제어하며, 필요 시 새로운 트랜잭션을 생성합니다.
독립적 관리 사례
REQUIRES_NEW
를 사용해 부모 트랜잭션과 독립된 새 트랜잭션을 생성.
📌 클래스 내부 호출과 트랜잭션 동작
💡 왜 클래스 내부에서 @Transactional
메서드를 호출하면 새로운 트랜잭션이 생성되지 않는가?
Spring의 @Transactional
은 프록시 객체를 통해 트랜잭션을 관리합니다.
그러나 클래스 내부에서 메서드를 호출하면 프록시 객체를 거치지 않고 직접 호출하기 때문에 새로운 트랜잭션이 생성되지 않습니다.
문제 상황 코드
public class MyService {
@Transactional
public void outerMethod() {
innerMethod(); // 프록시 객체를 거치지 않고 호출
}
@Transactional
public void innerMethod() {
// 트랜잭션 동작하지 않음
}
}
프록시를 거치지 않아 발생하는 문제
outerMethod()
는 트랜잭션이 적용되지만, 내부 호출(innerMethod()
)은 적용되지 않음.- 내부 호출은 프록시 객체를 우회하기 때문.
💡 Spring AOP의 프록시 구조
프록시 객체란?
Spring의 AOP는 프록시 패턴을 기반으로 동작합니다.
- 프록시 객체는 실제 메서드를 호출하기 전/후에 부가 기능(트랜잭션 관리)을 추가합니다.
프록시 호출 흐름
[클라이언트] → [프록시 객체] → [실제 객체]
- 트랜잭션 시작, 커밋, 롤백 등은 모두 프록시 객체에서 처리.
내부 호출 문제 발생 이유
- 클래스 내부에서 호출 시 [클라이언트] → [프록시] 구조를 우회하여, 실제 객체의 메서드가 직접 호출됩니다.
✅ 해결 방법
1️⃣ AopContext.currentProxy()
를 사용
Spring에서 제공하는 AopContext.currentProxy()
를 이용해 현재 객체의 프록시를 가져와 호출합니다.
수정된 코드
public class MyService {
@Transactional
public void outerMethod() {
((MyService) AopContext.currentProxy()).innerMethod(); // 프록시를 통해 호출
}
@Transactional
public void innerMethod() {
// 트랜잭션 적용
}
}
2️⃣ 내부 호출 대상 메서드를 별도 빈으로 분리
다른 클래스에 메서드를 정의하여 외부 호출이 프록시를 거치도록 설계합니다.
분리된 클래스 코드
@Component
public class InnerService {
@Transactional
public void innerMethod() {
// 새로운 트랜잭션 생성
}
}
@Component
public class MyService {
@Autowired
private InnerService innerService;
@Transactional
public void outerMethod() {
innerService.innerMethod(); // 프록시를 거쳐 호출
}
}
📌 프록시 객체와 트랜잭션이 클래스 외부 호출에서만 동작하는 이유
프록시 객체 동작의 본질
Spring은 JDK 동적 프록시 또는 CGLIB 프록시를 사용하여 트랜잭션을 제어합니다.
- JDK 동적 프록시: 인터페이스 기반.
- CGLIB 프록시: 클래스 상속 기반.
클래스 외부 호출이 필요한 이유
- 프록시 객체는 클라이언트 호출을 가로채 트랜잭션 관리.
- 내부 호출은 프록시 객체를 우회하므로 트랜잭션 동작이 적용되지 않음.
📌 Spring 트랜잭션에서 내부 호출이 프록시를 거치지 않는다는 것은 무슨 의미인가?
내부 호출 동작 원리
- Spring은 AOP 프록시를 통해 트랜잭션을 제어.
- 내부 호출은 프록시 객체를 거치지 않고 실제 객체 메서드를 직접 호출.
문제 해결 방법
- AopContext 사용: 프록시 객체를 직접 참조.
- 빈 분리: 호출 메서드를 별도 빈으로 분리.
📌 메서드 단위의 @Transactional
은 클래스 전체에 영향을 미치는가?
적용 범위
- 메서드 단위로 트랜잭션이 적용되며, 호출된 메서드에만 영향을 미칩니다.
- 클래스 레벨에 선언하면, 클래스의 모든 메서드가 트랜잭션 대상이 됩니다.
주의
- 클래스 레벨 선언 시에도 프록시를 거치지 않으면 동작하지 않음.
📌 요약
@Transactional
의 동작 원리- AOP 기반 프록시 객체를 통해 트랜잭션을 제어.
- 내부 호출 시 프록시를 거치지 않아 트랜잭션 미적용 문제 발생.
- 내부 호출 문제 해결 방법
AopContext.currentProxy()
를 사용해 프록시를 강제로 호출.- 호출 메서드를 별도 빈으로 분리.
- 클래스 외부 호출이 필요한 이유
- 프록시 객체는 외부 호출을 가로채 트랜잭션 제어.
- 메서드 단위의 적용
@Transactional
은 기본적으로 메서드 단위로 적용되며, 클래스 레벨에 선언 시 모든 메서드가 트랜잭션 대상.
반응형
'Java' 카테고리의 다른 글
[JAVA] @Transactional 알아보기 Part.3 #격리(Isolation) (2) | 2024.12.26 |
---|---|
[JAVA] @Transactional 알아보기 Part.2 #전파(Propagation) (0) | 2024.12.24 |
[JAVA] Spring 공통 모듈을 패키지화하고 GitHub Packages에 등록하는 방법 (0) | 2024.12.06 |
[JAVA] Java로 Excel 파일 생성 및 관리: Apache POI 사용법 (0) | 2024.11.26 |
[JAVA] Java의 예외 계층 구조와 개념 (4) | 2024.11.15 |