트랜잭션이란 여러 개의 작업들을 하나의 그룹으로 묶어서 처리하는 단위이다.
ACID 원칙
- 원자성(Atomicity): 하나의 그룹 단위는 모두 성공하거나, 모두 실패해야 한다.
- 일관성(Consistency): 트랜잭션이 성공적으로 종료되면, 비즈니스 로직의 의도대로 일관성 있게 처리돼야 한다.
- 격리성(Isolation): 여러 개의 트랜잭션이 실행될 때, 서로 영향을 주지 않고 독립적으로 실행되어야 한다.
- 지속성(Durability): 트랜잭션이 완료되면 DB가 종료되어도 데이터는 물리 저장소에 저장되어 결과가 지속되어야 한다.
커밋과 롤백
- 커밋: 모든 작업을 최종적으로 DB에 반영한다. 이 명령을 수행하면 하나의 트랜잭션 과정이 종료된다.
- 롤백: 작업 중 문제가 생겼을 때, 트랜잭션 내 수행된 작업을 취소한다.
JPA API로 commit을 수행하기 위해 여러 클래스를 거친다. JPA 구현체인 하이버네이트에서 JDBC API의 구현체인 H2까지 이어진다. JPA 기술을 사용한 데이터베이스와의 인터랙션은 내부적으로는 JDBC API를 통해서 이루어진다.
트랜잭션: 로컬 트랜잭션, 분산 트랜잭션
Spring: 선언형 방식, 프로그래밍 코드 베이스 방식
Spring에서의 적용
선언형 방식
트랜잭션은 애플리케이션의 부가 기능이기 때문에 AOP 적용 대상이다.
🌿 비즈니스 로직에 @Transational 추가
클래스 레벨에 적용
메서드에 일괄 적용된다.
@Service
@Transactional
public class MemberService {
...
}
체크 예외(Exception, SQLException, DataFormatException 등)는 이 방법으로 rollback이 되지 않는다.
별도의 예외 전략이 필요하지 않을 경우 아래와 같이 직접 지정해 주거나, 언체크 예외로 감싼다.
@Transactional(rollbackFor = {SQLException.class, DataFormatException.class})
메서드 레벨에 적용 (+ 클래스 레벨)
클래스 + 메서드 레벨에 적용하면 메서드 레벨의 애너테이션이 적용된다. 조회 메서드는 readonly를 true로 설정해서 JPA가 자체적으로 성능 최적화 과정을 거치도록 하는 게 좋다.
- 내부적으로 영속성 컨텍스트를 flush 하지 않는다.
- 변경 감지를 위한 스냅샷 생성을 하지 않는다.
@Service
@Transactional
public class MemberService {
...
@Transactional(readOnly = true) //읽기 전용 트랜잭션
public Member findMember(long memberId) {
return findVerifiedMember(memberId);
}
...
}
Creating new transaction with name [com.codestates.member.service.MemberService.findMember]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
트랜잭션 전파
트랜잭션의 경계에서 진행 중인 트랜잭션이 존재할 때, 존재하지 않을 때 어떻게 동작할 것인지 결정한다. 서로 다른 Service에서 이뤄지는 같은 분류의 작업이 있을 때, 이는 하나의 트랜잭션에 존재하도록 해야 한다.
//진행 중인 트랜잭션이 없으면 새로 시작하고, 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여
/* 적용된 메서드를 다른 Service 클래스에서 호출했을 때,
해당 클래스에 트랜잭션이 진행되고 있으면 그곳에 참여한다. */
@Transactional(propagation = Propagation.REQUIRED)
//무조건 새로운 트랜잭션 시작, 진행 중인 트랜잭션은 새로운 트랜잭션이 종료될 때까지 중지
@Transactional(propagation = Propagation.REQUIRES_NEW)
//진행 중인 트랜잭션이 없으면 예외 발생
@Transactional(propagation = Propagation.MANDATORY)
//트랜잭션을 필요로 하지 않는다 : 진행 중인 게 있으면 메서드 종료 시점까지 중지
@Transactional(propagation = Propagation.*NOT_SUPPORTED*)
//트랜잭션을 필요로 하지 않는다 : 진행 중인 게 있으면 예외 발생
@Transactional(propagation = Propagation.*NEVER*)
트랜잭션 격리 레벨
@Transactional(isolation = Isolation.DEFAULT)
//다른 트랜잭션에서 커밋하지 않은 데이터를 읽는 것을 허용
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
//다른 트랜잭션에 의해 커밋된 데이터를 읽는 것을 허용
@Transactional(isolation = Isolation.READ_COMMITTED)
//트랜잭션 내에서 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 한다
@Transactional(isolation = Isolation.REPEATABLE_READ)
//동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행되지 못하도록 한다
@Transactional(isolation = Isolation.SERIALIZABLE)
🌿 AOP 방식을 이용하여 비즈니스 로직에서는 감추는 방법
- Configuration 클래스 정의: @Configuration
- TransactionManager DI
- 트랜잭션 어드바이스용 TransactionInterceptor 빈 등록
- Advisor 빈 등록
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/interceptor/TransactionInterceptor.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/TransactionManager.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.html
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot] JUnit을 이용하여 테스트 코드 작성하기 (feat. Mockito) (0) | 2022.09.07 |
---|---|
ApplicationEventPublisher: How to use events in Spring | @Async (0) | 2022.09.05 |
비즈니스 예외 던지기 및 예외 처리 (0) | 2022.08.24 |
Spring MVC 패턴의 예외 처리 | @ExceptionHandeler, @RestControllerAdvice (0) | 2022.08.24 |
서비스 계층에서의 DI (0) | 2022.08.23 |
댓글