You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
선언적 트랜잭션 방식을 사용하면 단순히 애노테이션 하나로 트랜잭션 적용 가능, 이 기능은 트랜잭션 관련 코드가 눈에 보이지 않고, AOP를 기반으로 동작, 실제 트랜잭션이 적용되고 있는지 아닌지 확인이 어려움
packagehello.springtx.apply;
importlombok.extern.slf4j.Slf4j;
importorg.junit.jupiter.api.Test;
importorg.springframework.aop.support.AopUtils;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.boot.test.context.TestConfiguration;
importorg.springframework.context.annotation.Bean;
importorg.springframework.stereotype.Component;
importorg.springframework.transaction.annotation.Transactional;
importorg.springframework.transaction.support.TransactionSynchronizationManager;
/** * 트랜잭션 적용 테스트 클래스 * - 트랜잭션 프록시가 적용되는지 확인 * - 트랜잭션이 실제 활성화되는지 확인 */@Slf4j@SpringBootTestpublicclassTxBasicTest {
@AutowiredBasicServicebasicService; // 테스트 대상 빈/** * AOP 프록시 확인 테스트 * - Spring이 @Transactional이 적용된 Bean을 프록시 객체로 감싸는지 확인 * - AopUtils.isAopProxy()를 사용하여 프록시 여부를 검증 */@TestvoidproxyCheck() {
// 예상 결과: "BasicService$$EnhancerBySpringCGLIB..."log.info("aop class={}", basicService.getClass());
assertThat(AopUtils.isAopProxy(basicService)).isTrue();
}
/** * 트랜잭션 활성화 여부 확인 테스트 * - @Transactional 적용 메서드(tx)와 일반 메서드(nonTx)의 차이를 확인 */@TestvoidtxTest() {
basicService.tx(); // 트랜잭션이 활성화됨basicService.nonTx(); // 트랜잭션이 활성화되지 않음
}
/** * 테스트 환경에서 사용할 Bean 등록 클래스 */@TestConfigurationstaticclassTxApplyBasicConfig {
@BeanBasicServicebasicService() {
returnnewBasicService();
}
}
/** * 트랜잭션이 적용된 서비스 클래스 */@Slf4jstaticclassBasicService {
/** * @Transactional이 적용된 메서드 * - 트랜잭션이 활성화되어야 함 */@Transactionalpublicvoidtx() {
log.info("call tx");
booleantxActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
/** * @Transactional이 적용되지 않은 일반 메서드 * - 트랜잭션이 활성화되지 않아야 함 */publicvoidnonTx() {
log.info("call nonTx");
booleantxActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
}
}
}
예상 로그 출력
proxyCheck() - 실행
AopUtils.isAopProxy() : 선언적 트랜잭션 방식에서 스프링 트랜잭션은 AOP 기반 동작
@transactional을 메서드나 클래스에 붙이면 해당 객체는 트랜잭션 AOP 적용의 대상이 됨,
결과적으로 실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록 됨, 그리고 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입 됨
스프링 컨테이너에 트랜잭션 프록시 등록
@transactional 애노테이션이 특정 클래스나 메서드에 하나라도 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록
실제 basicService 객체 대신에 프록시인 basicService$$CGLIB를 스프링 빈에 등록, 그리고 프록시는 내부에 실제 basicService 참조
핵심은 실제 객체 대신에 프록시가 스프링 컨테이너에 등록 되었다는 점
클라이언트인 txBasicTest 는 스프링 컨테이너에 @Autowired BasicService basicService 의존관계 주입을 요청, 스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시 주입
프록시는 BasicService를 상속해서 만들어지기 때문에 다형성을 활용, 따라서 BasicService
대신에 프록시인 BasicService$$CGLIB 를 주입
트랜잭션 프록시 동작 방식
클라이언트가 주입 받은 basicService$$CGLIB는 트랜잭션을 적용하는 프록시
로그 추가
위 로그를 추가하면 트랜잭션 프록시가 호출하는 트랜잭션의 시작과 종료를 명확하게 로그로 확인 가능
basicService.tx() 호출
클라이언트가 basicService.tx()를 호출하면, 프록시의 tx()가 호출, 여기서 프록시는 tx() 메서드 트랜잭션을 사용할 수 있는지 확인, tx() 메서드에는 @transactional이 붙어 있어 트랜잭션 적용 대상
따라서 트랜잭션을 시작한 다음에 실제, basicService.tx() 호출
실제 basicService.tx()의 호출이 끝나서 프록시로 제어가(리턴) 돌아오면 프록시는 트랜잭션 로직을 커밋하거나 롤백 트랜잭션을 종료 함
basicService.nonTx() 호출
클라이언트가 basicService.nonTx() 호출하면, 트랜잭션 프록시의 nonTx() 호출 여기서 nonTx() 메서드가 트랜잭션을 사용할 수 있는지 확인해본다. nonTx() 에는 @transactional 이 없으므 로 적용 대상이 아님
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
트랜잭션
JDBC 트랜잭션 코드 VS JPA 트랜잭션 코드
JDBC 트랜잭션 코드
JPA 트랜잭션 코드
PlatformTransactionManager 인터페이스
JPA를 사용하면 JpaTransactionManager를 스프링 빈으로 등록해 줌
스프링 트랜잭션 사용 방식
선언적 트랜잭션 관리(Declarative Transaction Management)
프로그래밍 방식의 트랜잭션 관리(programmatic transaction management)
선언적 트랜잭션과 AOP
AOP가 적용
서비스 계층의 트랜잭션 사용 코드 예시
트랜잭션 프록시 코드 예시
트랜잭션 프록시 적용 후 서비스 코드 예시
프록시 도입 후 전체 과정
랜잭션 동기화 매니저를 통해 리소스<커넥션>를 동기화 함
스프링이 제공하는 트랜잭션 AOP
@transactional
org.springframework.transaction.annotation.Transactional트랜잭션 적용 확인
예상 로그 출력
proxyCheck() - 실행
결과적으로 실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록 됨, 그리고 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입 됨
스프링 컨테이너에 트랜잭션 프록시 등록
대신에 프록시인 BasicService$$CGLIB 를 주입
트랜잭션 프록시 동작 방식
로그 추가
basicService.tx() 호출
basicService.nonTx() 호출
TransactionSynchronizationManager.isActualTransactionActive()
실행 결과
트랜잭션 적용 위치
테스트 출력 로그
@transactional 두 가지 규칙
1-1. LevelService 의 타입에 @transactional(readOnly = true) 이 붙음
1-2. write() : 해당 메서드에 @transactional(readOnly = false) 이 붙음
1-2-1. 이렇게 되면 타입에 있는 @transactional(readOnly = true) 와 해당 메서드에 있는
@transactional(readOnly = false) 둘 중 하나를 적용
1-2-2. 클래스 보다는 메서드가 더 구체적이므로 메서드에 있는
@transactional(readOnly = false) 옵션을 사용한 트랜잭션이 적용
2-1. read() : 해당 메서드에 @transactional이 없다. 이 경우 더 상위인 클래스 확인
2-1-1. 클래스에 @transactional(readOnly = true) 적용, 따라서 트랜잭션이 적용되고 readOnly = true 옵션을 사용하게 됨
TransactionSynchronizationManager.isCurrentTransactionReadOnly
실행 결과
All reactions