스프링 트랜잭션 부분 정리
JDBC ,JPA 는 트랜잭션 사용 코드가 다름


PlatformTransactionManager를 통해 추상화 시켰다.
트랜잭션을 트랜잭션 시작, 커밋, 롤백으로 단순하게 추상화 할 수 있다.

PlatFormTransactionManager를 사용하는 방법은 크게 2가지가 있다.
선언적 트랜잭션 관리 vs 프로그래밍 방식 트랜잭션 관리
선언전 트랜잭션 관리(Declarative Transaction Management)
@Transactional 애노테이션 하나만 선언해서 매우 편리하게 트랜잭션을 적용하는 것을 선언적 트랜잭션 관리라 한다.
프로그래밍 방식의 트랜잭션 관리(programmatic transaction management) 트랜잭션 매니저 또는 트랜잭션 템플릿 등을 사용해서 트랜잭션 관련 코드를 직접 작성하는 것을 프로그래밍 방식의 트랜잭션 관리라 한다.
TransactionStatus status = transactionManager.getTransaction(new
DefaultTransactionDefinition());
try {
//비즈니스 로직
bizLogic(fromId, toId, money);
transactionManager.commit(status); //성공 시커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}

public class TransactionProxy {
private MemberService target;
public void logic() {
//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(..);
try {
//실제 대상 호출
target.logic();
transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}
}
}

스프링 컨테이너가 빈 설정 정보(예: @Configuration, @Component 등)를 읽어들입니다.
특정 빈을 생성할 때, 의존성 주입(DI)을 먼저 수행합니다.
의존성 주입이 완료되면 빈의 초기화 콜백 메서드(예: @PostConstruct, afterPropertiesSet)를 호출합니다.
초기화가 완료된 직후, 스프링은 BeanPostProcessor라는 인터페이스를 통해 빈 객체에 추가적인 처리를 할 기회를 갖습니다.
AOP를 위한 BeanPostProcessor의 구현체(예: AnnotationAwareAspectJAutoProxyCreator)가 이때 동작합니다. 이 구현체는 해당 빈이 AOP 적용 대상인지 확인합니다.
만약 AOP 적용 대상이라면, 이 시점에서 원본 객체를 감싼 프록시(Proxy) 객체를 생성합니다.
그리고 이 프록시 객체를 최종 빈으로서 스프링 컨테이너에 반환합니다.


TransactionSynchronizationManager.isActualTransactionActive()를 통해 현재 쓰레드에 트랜잭션이 적용되어 있는지 확인할 수 있다.
스프링에서 우선 순위는 항상 더 구체적이고 자세한 것이 높은 우선 순위를 가진다. 따라서 메서드와 클래스 둘 다에 애노테이션이 붙어 있다면 더 구체적인 메서드가 더 높은 우선순위를 가진다.
@Transactional 규칙
우선순위 : 클래스보다 메서드에 붙은게 더 우선
클래스에 적용하면 메서드는 자동 적용
트랜잭션 AOP 주의 사항 - 프록시 내부 호출
트랜잭션을 적용하려면 항상 프록시를 통해 대상 객체 (Target)을 호출해야한다.
만약 target 내에서 내부 함수를 호출한다면 내부 함수에 걸린 Transactional은 적용되지 않는 문제가 발생한다.


callService의 트랜잭션 프록시가 호출된다.
external() 메서드는 Transactional이 없기 때문에 트랜잭션 프록시는 트랜잭션을 적용하지 않는다.
트랜잭션을 적용하지 않고, 실제 callService 객체 인스턴스의 external)을 호출한다.
내부에서 internal() 메서드를 호출한다. this.internal()을 통해 실제 대상 객체(target)의 인스턴스를 통해 target에 있는 internal()이 직접 호출되게 됐다.
이를 해결하기 위해 별도의 클래스로 분리할 수도 있다.








외부 트랜잭션에서 내부 트랜잭션을 실행하면 하나의 트랜잭션으로 묶인다.
원칙이 있는데 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.







내부 트랜잭션은 물리 트랜잭션을 롤백하지 않는 대신에 트랜잭션 동기화 매니저에 rollbackOnly=true라는 표시를 해둔다.
논리 트랜잭션이 하나로도 롤백되면 물리 트랜잭션은 롤백된다.
내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시한다.
외부 트랜잭션을 커밋할 때 롤백 전용 마크를 확인한다. 롤백 전용 마크가 표시되어 있으면 물리 트랜잭션을 롤백하고, UnexpectedRollbackException 예외를 던진다.
그래서 외부 트랜잭션과 내부 트랜잭션을 분리해서 어떻게 사용할까?

내부 트랜잭션을 시작할 때 Propagation Requires new 옵션을 준다.





신규 트랜잭션이 아니므로 안에 rollbackOnly true 보고 롤백 되버림





Last updated