데이터베이스에서 논리적 상태 변화, 즉 Insert, Update, Delete로 데이터베이스의 데이터가 변화가 있는 것을 트랜잭션(transaction)이라고 한다.
ACID 원칙
1. 원자성(Atomicity)
둘 다 성공하던가 둘 다 실패하던가 중에서 하나로만 처리되는 것이 보장되어야 한다.
2. 일관성(Consistency)
트랜잭션이 에러없이 성공적으로 종료될 경우, 일관성있게 저장되거나 변경되는 것을 의미한다.
3. 격리성(Isolation)
여러개의 트랜잭션이 실행될 경우 각각 독립적으로 실행되어야 한다.
4. 지속성(Durability)
트랜잭션이 완료되면 그 결과는 지속적이어야 한다.
비즈니스 로직을 수행하는 서비스 단에 @Transactional
어노테이션을 붙여 트랜잭션을 적용할 수 있다. 서비스 클래스나 서비스 클래스의 메서드 레벨에서 어노테이션을 붙일 수 있으며, 클래스 레벨에서 어노테이션을 붙일 경우 메서드에 일괄 적용이 된다. 클래스 레벨, 메서드 레벨 둘다 붙일 경우 메서드 레벨의 @Transactional
어노테이션이 적용된다.
조회 메서드에는 @Transactional(readOnly = true)
로 설정하여 자체적으로 성능 최적화 과정을 거치도록 한다. readOnly 옵션을 true로 설정하면 JPA 내부적으로 영속성 컨텍스트를 flush 하지 않고 읽기 전용 트랜잭션일 경우 변경 감지를 위한 스탭샷 생성도 진행하지 않는다. flush 처리도, 스냅샷도 생성하지 않으므로 불필요한 추가 동작을 줄일 수 있다. 그리고 체크 예외는 @Transactional
어노테이션만 추가해서는 rollback이 되지 않아 @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})
와 같이 해당 체크 예외를 직접 지정해주거나 언체크 예외(unchecked exception)로 감싸야 rollback 기능을 적용할 수 있다
AController에서 AService를 호출하는데 있어서 AService 내에서는 BService 메서드를 호출하고 있다. 이때 여러 작업이 하나의 트랜잭션으로 묶이게 된다.
이때 BService로직에서 예외가 발생하든, AService에서 예외가 발생하든 두 메서드가 하나의 트랜잭션 경계 내에 있으므로 모두 rollback이 된다.
트랜잭션 경계에서 진행 중인 트랜잭션이 존재할 때 또는 존재하지 않을 때, 어떻게 동작할 것인지 결정하는 것을 말한다.
트랜잭션 전파는 propagation 속성을 통해서 설정할 수 있다.
속성 | 설명 |
---|---|
Propagation.REQUIRED | 디폴트 값으로 진행 중인 트랜잭션이 없으면 새로 시작하고, 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여 |
Propagation.REQUIRES_NEW | 이미 진행중인 트랜잭션과 무관하게 새로운 트랜잭션이 시작, 기존에 진행중인 트랜잭션은 새로 시작된 트랜잭션이 끝날때까지 중지 |
Propagation.MANDATORY | 진행 중인 트랜잭션이 없으면 새로운 트랜잭션이 실행, 이미 진행되고 있는 트랜잭션이 있으면 예외 발생 |
Propagation.NOT_SUPPORTED | 트랜잭션이 필요하지 않다는 것을 의미, 진행 중인 트랜잭션이 있으면 메서드 실행이 종료될 때까지 진행중인 트랜잭션은 중지되며, 메서드 실행이 종료되면 트랜잭션을 계속 진행 |
Propagation.NEVER | 트랜잭션이 필요 없음을 의미, 진행 중인 트랜잭션이 있으면 예외 발생 |
트랜잭션은 다른 트랜잭션에 영향을 주지 않고 독립적으로 실행되어야 하는 격리성이 보장되어야 하는데 이를 isolation 속성을 통해 설정할 수 있다.
속성 | 설명 |
---|---|
Isolation.DEFAULT | 데이터베이스에서 제공하는 기본 값 |
Isolation.READ_UNCOMMITTED | 다른 트랜잭션에서 커밋하지 않은 데이터를 읽는 것을 허용 |
Isolation.READ_COMMITTED | 다른 트랜잭션에 의해 커밋된 데이터를 읽는 것을 허용 |
Isolation.REPEATABLE_READ | 트랜잭션 내에서 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 한다 |
Isolation.SERIALIZABLE | 동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행되지 못하도록 한다 |
@Configuration
public class TxConfig {
private final TransactionManager transactionManager;
// TransactionManager DI
public TxConfig(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
// 트랜잭션 어드바이스용 TransactionInterceptor 빈 등록
@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);
txAttributeSource.setNameMap(txMethods);
return new TransactionInterceptor(transactionManager, txAttributeSource);
}
@Bean
public Advisor txAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// 포인트 컷 지정
pointcut.setExpression("execution(* 패키지명.서비스클래스.*(..))");
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}