Spring에서 @Transactional은 AOP(Aspect-Oriented Programming)를 사용하여 트랜잭션을 처리
@Transactional의 동작 방식
- AOP Proxy 생성:
- Spring은 @Transactional이 선언된 메서드나 클래스에 대해 AOP 프록시를 생성
- 이 프록시는 원래의 메서드를 감싸는 대리 객체로, 실제 메서드 호출 전에 트랜잭션을 시작하고 메서드 실행 후에 트랜잭션을 커밋하거나 롤백
- 트랜잭션 시작:
- 트랜잭션을 관리하는 AOP 프록시는 메서드 호출 전에 트랜잭션을 시작
- 이때 트랜잭션 관리자는 데이터베이스 연결을 확보하고, 트랜잭션 상태를 관리
- 메서드 실행:
- AOP 프록시가 실제 메서드를 호출
- 이 메서드는 비즈니스 로직을 처리
- 트랜잭션 커밋 또는 롤백:
- 메서드 실행이 성공적으로 완료되면 AOP 프록시는 트랜잭션을 커밋
- 만약 메서드 실행 중 예외가 발생하면 AOP 프록시는 트랜잭션을 롤백
- 트랜잭션을 롤백할지 여부는 예외의 종류(예: RuntimeException과 같은 unchecked 예외)에 따라 달라짐
AOP 프록시의 종류
Spring에서는 두 가지 주요 프록시 방식 존재
- JDK 동적 프록시:
- 인터페이스 기반 프록
- 대상 클래스가 인터페이스를 구현하고 있을 때, 해당 인터페이스를 기반으로 프록시 객체를 생성
- CGLIB 프록시:
- 클래스 기반 프록시
- 클래스가 인터페이스를 구현하지 않거나, @Transactional이 붙은 메서드가 인터페이스가 아닌 클래스 메서드일 때 사용
- CGLIB는 원본 클래스의 서브클래스를 만들어 프록시를 생성
@Transactional과 Proxy
Spring에서 @Transactional은 AOP의 기능을 사용하여 프록시를 생성하고 이를 통해 트랜잭션을 관리
프록시는 실제 비즈니스 로직을 감싸서 트랜잭션을 시작하고 종료하는 작업을 수행
Proxy 생성 과정:
- Bean 생성: Spring은 @Transactional이 선언된 클래스의 빈을 관리
- 프록시 객체 생성: @Transactional이 있는 클래스의 메서드 호출 시, Spring은 이 클래스를 감싸는 프록시 객체를 생성
- 트랜잭션 처리: 프록시 객체는 실제 메서드 호출 전에 트랜잭션을 시작하고, 메서드 실행 후에는 트랜잭션을 커밋하거나 롤백
- 프록시 사용: 클라이언트는 실제 @Transactional이 붙은 메서드를 호출하지 않고, 대신 프록시를 통해 메서드를 호출합니다. 프록시가 트랜잭션을 관리하는 방식으로 동작
Bean과 Proxy의 관계
- 프록시를 통한 Bean 접근:
- Spring 컨테이너에서 관리되는 빈은 @Transactional을 적용하면 프록시를 통해 호출
- 실제 메서드를 호출하는 것이 아니라, 프록시 객체가 해당 메서드를 가로채서 트랜잭션을 시작하고 관리
- 빈의 생성과 프록시의 결합:
- @Transactional을 사용하는 클래스는 Spring 컨테이너에 의해 빈으로 관리
- Spring은 이 빈을 생성할 때, 해당 클래스에 트랜잭션을 관리하는 프록시를 추가
- 프록시 객체는 목표 객체(target object), 즉 원래의 @Transactional이 적용된 클래스를 감싸고 있으며, 이 프록시를 통해 트랜잭션 관련 로직이 실행
@Transactional을 사용할 때 주의점
1. 자기 호출 (Self-invocation)
- 문제: 같은 클래스 내에서 @Transactional이 적용된 메서드가 다른 메서드를 호출하면, 트랜잭션이 새로 시작되지 않고 기존 트랜잭션이 유지됨. 즉, 트랜잭션이 제대로 분리되지 않거나, 프록시가 제대로 작동하지 않기 때문에 트랜잭션의 기대 동작이 일어나지 않음
- 해결: 트랜잭션을 분리하려면 메서드를 다른 Bean으로 분리하여 외부에서 호출
@Service
public class MyService {
@Autowired
private AnotherService anotherService;
@Transactional
public void method1() {
// 트랜잭션 시작
anotherService.method2(); // 다른 Bean을 호출
}
}
@Service
public class AnotherService {
@Transactional
public void method2() {
// 트랜잭션 시작
}
}
2. 트랜잭션 전파 (Propagation)
- 문제: @Transactional은 기본적으로 REQUIRED 전파 옵션을 사용. 즉, 이미 트랜잭션이 시작되어 있으면 해당 트랜잭션을 사용하고, 없으면 새로운 트랜잭션을 시작. 때로는 트랜잭션 전파 방식을 명시적으로 설정
- 해결: 필요한 트랜잭션 전파 방식에 맞게 propagation 속성을 설정. 예를 들어, REQUIRES_NEW를 설정하면 항상 새로운 트랜잭션을 시작
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void someMethod() {
// 항상 새로운 트랜잭션을 시작
}
3. 예외 처리 (Exception Handling)
- 문제: @Transactional은 기본적으로 런타임 예외(RuntimeException 및 그 하위 클래스)만 롤백을 트리거함.
반면에 체크 예외(Checked Exception)는 기본적으로 롤백을 발생시키지 않음.
예를 들어, SQLException이나 IOException 같은 체크 예외는 롤백되지 않음. - 해결: 체크 예외도 롤백하도록 하려면 @Transactional의 rollbackFor 속성을 사용하여 명시적으로 지정.
@Transactional(rollbackFor = SQLException.class)
public void someMethod() throws SQLException {
// SQLException이 발생하면 롤백
}
4. 트랜잭션 격리 수준 (Isolation)
- 문제: 트랜잭션의 격리 수준은 데이터베이스의 동시성 및 무결성에 영향을 미침
기본적으로 @Transactional은 Isolation.DEFAULT로 설정되어 있으며, 이는 데이터베이스 기본값을 따름
하지만, 특정 요구 사항에 따라 격리 수준을 설정할 필요가 있음 - 해결: 트랜잭션의 격리 수준을 변경하려면 isolation 속성을 설정
@Transactional(isolation = Isolation.READ_COMMITTED)
public void someMethod() {
// 트랜잭션 격리 수준을 READ_COMMITTED로 설정
}
'Java > Spring' 카테고리의 다른 글
Command 패턴 (0) | 2025.03.17 |
---|---|
Proxy 패턴 (0) | 2025.03.12 |
[ERROR] javax.servlet.ServletRequest.getRemoteAddr() is not supported (0) | 2023.06.28 |
@Transactional 적용 안된 현상 정리 (0) | 2022.04.09 |
Controller parameter 받는 방법 (0) | 2022.04.09 |