[Transaction] 트랜잭션이란? / 스프링 지원 - 선언형 방식 트랜잭션 (어노테이션, AOP)
트랜잭션(Transaction) 이란?
: 여러개의 작업들을 하나의 그룹으로 묶어서 처리하는 처리 단위
무조건 여러개의 작업을 그룹으로 묶는게 트랜잭션? ➡ 🙅🏻♀️Xxx!
물리적으로 여러 개의 작업이지만, 논리적으로 마치 하나의 작업처럼 인식,
전부 성공 or 전부 실패(All or Nothing) 둘 중 하나로만 처리 되어야 트랜잭션!!
db, 파일, 메세지 등 전혀 다른 타입의 리소스를 하나의 작업단위로 묶어서 처리해야 할 수도 있다.
트랜잭션의 특징 - ACID 원칙
1. 원자성 (Atomicity)
: 작업을 더 이상 쪼갤 수 없음
논리적으로 하나의 작업으로 인식함 ➡ All or Nothing 중 하나로만!
2. 일관성 (Consistency)
: 트랜잭션 성공적 종료시, 일관성 있게 저장, 변경됨
비즈니스 로직이 의도한대로 저장, 변경되어야함!
3. 격리성 (Isolation)
: 여러 개의 트랜잭션 실행시, 각각 독립적으로 실행되어야 함
각 트랜잭션이 다른 트랜잭션에 영향 주지 않고, 독립적으로 실행되어야 함!
* db - 여러 트랜잭션을 성능 향상을 목적으로 아주 빠른 속도로 번갈아가면서 처리하고 있는 것
4. 지속성 (Durability)
: 트랜잭션 완료? 결과는 지속되어야 한다.
db가 종료되어도 데이터가 저장소에 저장되어 지속적으로 유지되어야 함!
트랜잭션 커밋(commit) & 롤백(rollback)
커밋, 롤백 : db에서 사용되는 명령어
1. 커밋 (commit)
= 모든 작업을 최종적으로 db에 반영하는 명령어
commit 실행 ➡ 변경된 내용 db에 영구 저장, 하나의 트랜잭션 종료!
commit 실행 안할경우 변경 내용 db에 반영 안됨
2. 롤백 (rollback)
= 작업 중 문제 발생시, 수행된 작업 취소
트랜잭션 시작 이 전의 상태로 되돌어감
JPA의 tx.commit() 동작과정 (흐름 이해만 하기)
JPA EntityTransaction 객체 tx로 tx.commit() 호출
⬇
EntityTransaction 인터페이스 구현 클래스인 TransactionImpl의 commit() 호출
⬇
TransactionImpl 은 물리적 트랜잭션 제어 위한
TransactionDriverComtrolImpl 얻은 후 commit() 다시 호출
⬇
TransactionDriverComtrolImpl 은 JDBC Connection 액세스 방법 제공하는
JdbcResourceTransaction 구현 객체인 AbstractLogicalCoonectionImplementor 의 commit() 다시 호출
⬇
AbstractLogicalCoonectionImplementor 에서
JDBC Connection 을 얻고 connection 객체의 commit() 다시 호출
----------------------------------------------------------------------------------------- ⬆ Hibernate ORM 영역 ⬆ -----------------
JDBC API 의 구현체인 JdbcConnection 영역으로 이동
(오늘 학습한 내용의 경우 H2)
⬇
H2에서 지원하는 Command 클래스에서
commit 명령, 실행 하는 메서드 호출
⬇
commitIfNonTransactional() 메서드 호출 : commit 명령 전달
: auto commit 여부 체크 후 Session 객체 통해 commit 명령 수행
(예외 발생 시 rollback 수행)
⬇
SessionLocal 클래스에서 트랜잭션 commit 수행
➡ JPA 이용한 db와의 인터랙션 = 내부적으로 JDBC API를 통해서 이루어짐!
Spring Framework 에서의 트랜잭션 처리
트랜잭션 : 로컬 트랜잭션 / 분산 트랜잭션
Spring 에서 사용되는 트랜잭션 방식 :
선언형 트랜잭션 방식 / 프로그래밍 코드 베이스 트랜잭션 방식
선언형 방식의 트랜잭션 적용
1. 비즈니스 로직에 어노테이션 추가 방식
2. AOP 방식 - 비즈니스 로직에서 트랜잭션 적용 코드를 감추는 방식
1. 어노테이션 방식의 트랜잭션 적용
= 트랜잭션이 필요한 영역에 @Transactional 붙이기!
클래스 레벨과 메서드 레벨의 트랜잭션 적용 순서
클래스 레벨에만 @Transactional이 적용된 경우
클래스 레벨의 @Transactional 애너테이션이 메서드에 일괄 적용
클래스 레벨과 메서드 레벨에 함께 적용된 경우
메서드 레벨의 @Transactional 애너테이션이 적용됩니다. (우선순위 제일 높음)
만약 메서드 레벨에 @Transactional 애너테이션이 적용되지 않았을 경우, 클래스 레벨의 @Transactional 애너테이션이 적용됩니다.
트랜잭션 작동 순서
메서드에 붙은 @Transactional 으로 새로운 트랜잭션이 생성되고,
⬇
트랜잭션에서 commit 이 일어난 다음,
⬇
트랜잭션이 종료된다.
⬇
그 다음에 JPA의 EntityManager(영속성 클래스 관리해줌) 도 종료된다.
* 체크 예외(checked exception) 은 @Transactional 만 추가해서는 rollback 이 되지 않는다.
캐치한 후에 예외 복구할지, 회피할지 전략이 필요하기 때문.
➡ @Transactionl(rollbackFor = {SQLException.class, DataFormatException.class}) 로 직접 지정해주거나
언체크 예외(unchecked excepion)로 감싸야 rollback 기능 적용 가능!
* @Transactional(readOnly = true) : 조회 메서드에 붙여줌
: commit 절차를 진행하기는 하지만, JPA 내부적으로 영속성 컨텍스트를 flush 하지 않고,
변경 감지를 위한 스냅샷 생성도 진행하지 않는다.
➡ 불필요한 추가 동작을 줄일 수 있다.
JPA 자체적으로 성능 최적화 과정을 거치기 위해 조회 메서드에 (readOnly = true) 붙여준것!
여러 작업이 하나의 트랜잭션으로 묶이는 경우
각각의 트랜잭션이 독립적으로 실행 된다면
한 메서드에서 오류 나고, 다른 한 메서드는 정상 반영 된다면 ACID 원칙 깨진다!
각각의 트랜잭션 경계션이 하나로 연결되는 부분 有
하나로 묶여있기 때문에 한 메서드가 오류나도 모두 rollback 된다.
➡ ex) @Transactional(propagation = Propagation.REQUIRED)
⬇
트랜잭션 애트리뷰트들
트랜잭션 전파 (Transaction Propagation)
: 트랜잭션 경계에서 진행중인 트랜잭션이 존재 할, 하지 않을때 어떻게 동작할 것인지 결정함
1. Propagation.REQUIRED (default)
: 진행중인 트랜잭션 없? ➡ 새로시작
있? ➡ 해당 트랜잭션에 참여
2. Propagation.REQUIRES_NEW
: 진행중인 트랜잭션과 무관, 새로운 트랜잭션 시작
기존 진행 트랜잭션 ➡ 새 트랜잭션 종료 때까지 중지
3. Propagation.MANDATORY
: 진행중인 트랜잭션 없? ➡ 예외 발생
4. Propagation.*NOT_SUPPORTED*
: 트랜잭션 필요 X
진행중인 트랜잭션 있? ➡ 메서드 실행 종료될때까지 중지
메서드 실행 종료되면 트랜잭션 계속 진행
5. Propagation.*NEVER*
: 트랜잭션 필요 X,
진행중인 트랜잭션 있? ➡ 예외발생
트랜잭션 격리 레벨 (Isolation Level)
: 트랜잭션 격리성 조절할 수 있는 옵션
1. Isolation.DEFAULT
: db 제공 기본값
2. Isolation.READ_UNCOMITTED
: 다른 트랜잭션에서 커밋하지 않은 데이터 읽기 허용
3. Isolation.READ_COMITTED
: 다른 트랜잭션에 의해 커밋된 데이터 읽기 허용
4. Isolation.REPEATABLE_READ
: 트랜잭션 내 동일 데이터 반복 조회해도 같은 데이터 조회
5. Isolation.SERIALIZABLE
: 동일한 데이터 ➡ 두 개 이상의 트랜잭션 동시 수행 불가
2. AOP 방식의 트랜잭션 적용
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;
import java.util.HashMap;
import java.util.Map;
// Configuration 클래스 정의
@Configuration
public class TxConfig {
private final TransactionManager transactionManager;
// TransactionManager DI(생성자)
public TxConfig(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Bean
public TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource txAttributeSource =
new NameMatchTransactionAttributeSource();
//트랜잭션 애트리뷰트 지정
// 조회 메서드 제외, 공통 메서드용
RuleBasedTransactionAttribute txAttribute =
new RuleBasedTransactionAttribute();
txAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 조회 메서드용
RuleBasedTransactionAttribute txFindAttribute =
new RuleBasedTransactionAttribute();
txFindAttribute.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRED);
txFindAttribute.setReadOnly(true);
// 트랜잭션 적용 대상 메서드에 애트리뷰트 매핑
Map<String, TransactionAttribute> txMethods = new HashMap<>();
txMethods.put("find*", txFindAttribute);
txMethods.put("*", txAttribute);
// Map 객체 넘겨 주기
txAttributeSource.setNameMap(txMethods);
// TransactionInterceptor 객체 생성
return new TransactionInterceptor(transactionManager, txAttributeSource);
}
@Bean
public Advisor txAdvisor() {
// 포인트컷 표현식 ➡ 타겟 클래스 적용
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.codestates.coffee.service." +
"CoffeeService.*(..))");
return new DefaultPointcutAdvisor(pointcut, txAdvice()); // Advisor 객체 생성
}
}
AOP 방식 트랜잭션 적용 순서
- AOP 방식으로 트랜잭션을 적용하기 위한 Configuration 클래스 정의
@Configuration 달아서 클래스 정의
⬇ - TransactionManager DI
생성자로 TransactionManager 객체 DI
⬇ - 트랜잭션 어드바이스용 TransactionInterceptor 빈 등록
TransactionInterceptor : 대상 클래스, 인터페이스에 트랜잭션 경계 설정 및 트랜잭션 적용 가능
트랜잭션 애트리뷰트 지정
메서드 이름 패턴에 따라 구분 적용 가능!
트랜잭션 적용할 메서드에 트랜잭션 애트리뷰트 매핑
Map의 key 이름 : 메서드 이름 패턴으로 지정
애트리뷰트 추가된 Map 객체를 txAttributeSource.setNameMap(txMethods)로 넘겨줌
TransactionInterceptor 객체 생성
(생성자 파라미터 transactionManager, txAttributeSource)
⬇ - Advisor 빈 등록
AspectJExpressionPointcut 객체 생성,
포인트 컷 지정 (TransactionInterceptor 타겟 클래스에 적용 위함)
Advisor 객체 생성 : DefaultPointcutAdvisor 생성자 파라미터로 포인트컷, 어드바이스 전달