학습 목표
- 트랜잭션(Transaction)이 무엇인지 이해할 수 있다.
두 개의 작업들이 마치 하나의 그룹처럼 묶여서 처리되는 중에 둘 중 하나라도 처리에 실패할 경우 애플리케이션의 신뢰성이 깨지는 상황이 발생한다. 이처럼 트랜잭션은 여러 개의 작업들을 하나의 그룹으로 묶어서 처리하는 처리 단위인데, 무조건 여러 개의 작업을 그룹으로 묶는다고 해서 트랜잭션이라고 부를 수 있는 게 아니라 물리적으로는 여러 개의 작업이지만 논리적으로는 마치 하나의 작업으로 인식해서 전부 성공하든지 전부 실패하든지 둘 중 하나로만 처리되어야 트랜잭션의 의미를 가진다.
이러한 All or Nothing이라는 트랜잭션 처리 방식은 애플리케이션에서 사용하는 데이터의 무결성을 보장하는 핵심적인 역할을 한다.
ACID원칙
트랜잭션의 특징을 이야기 할 때는 일반적으로 ACID 원칙을 이용한다.
- 원자성(Atomicity) 트랜잭션에서의 원자성이란 작업을 더이상 쪼갤 수 없음을 의미한다.
논리적으로 하나의 작업으로 인식해서 둘다 성공하든지 둘다 실패하든지 둘중 하나로만 처리되는 것이 보장되어야 한다 - 일관성(Consistency) 일관성은 트랜잭션이 에러없이 성공적으로 종료될 경우, 비즈니스 로직에서 의도하는대로 일관성있게 저장되거나 변경되는 것을 의미한다. 100만원을 인출했는데 120만원이 인출되었다고 조회된다면 일관성에 위배 되는 것이다.
- 격리성(Isolation) 격리성은 여러 개의 트랜잭션이 실행될 경우 각각 독립적으로 실행이 되어야 함을 의미한다
격리성을 이해하기 위해서는 CPU가 프로세스를 처리하는 과정을 이해하고 있는것이 좋다
예를들어 우리가 컴퓨터에서 워드 작업을 하고있고, 동시에 뮤직 플레이어로 음악을 듣고있다면 우리눈에는 보이지 않지만 CPU는 위 두가지 프로세스를 아주 빠른 속도로 번갈아가면서 실행을 시킨다
이처럼 데이터베이스 역시 성능 향상을 목적으로 한 개 이상의 트랜잭션을 번갈아가면서 처리할 수 있는데, 이 경우 각 트랜잭션이 다른 트랜잭션에 영향을 주지 않고 독립적으로 실행이 되어야 한다는 것이 바로 격리성이다. - 지속성(Durability) 트랜잭션이 완료되면 그 결과는 지속되어야 한다는 의미.
즉, 지속성은 데이터베이스가 종료되어도 데이터는 물리적인 장소에 저장되어 지속적으로 유지되어야한다는 의미이다.
트랜잭션 커밋 과 롤백
커밋과 롤백은 데이터베이스에서 사용되는 명령어이다.
커밋(Commit)
- 커밋은 모든 작업을 최종적으로 데이터베이스에 반영하는 명령어로써 commit명령을 수행하면 변경된 내용이 데이터베이스에 영구적으로 저장된다.
- 만약 commit명령을 수행하지 않으면 작업의 결과가 데이터베이스에 최종적으로 반영되지 않는다.
- commit 명령을 수행하면, 하나의 트랜잭션 과정은 종료하게 된다.
롤백
- 롤백은 작업 중 문제가 발생했을 때, 트랜잭션 내에서 수행된 작업들을 취소한다(따라서 트랜잭션 시작 이전의 상태로 되돌아간다)
트랜잭션은 데이터베이스에만 한정해서 사용하는 의미는 아니다 예를 들면 어떤 데이터를 로컬 데이터베이스에도 저장하고, 그 결과를 푸시 알림으로 클라이언트에게 전송하는 기능이 있다면 데이터베이스 저장과 푸시 알림 전송이라는 두 개의 작업이 하나의 트랜잭션으로 묶여서 둘 중에 하나라도 실패할 경우 롤백이 되어야 할 수도 있다 이처럼 전혀 다른 타입의 리소스(데이터베이스, 파일, 메시지 등)를 하나의 작업 단위로 묶어서 처리해야 되는 상황에서는 어떤 식으로 트랜잭션을 적용하면 좋을지 고민하는 것도 좋다.
여러 작업이 하나의 트랜잭션으로 묶이는 경우
![](https://blog.kakaocdn.net/dn/bQdW1e/btrQpalIVKm/m5gndblA5rBxPBkK0CtWr0/img.png)
같이 주문 정보를 저장하는 작업과 주문한 커피 수 만큼의 스탬프 수를 업데이트하는 작업이 차례차례 실행된다고 생각해보면,
OrderService에서 createOrder() 메서드를 호출할 경우, 내부에서 주문 정보 저장을 위한 트랜잭션이 하나 시작되며, 다음으로 memberService.updateStamp() 메서드 호출을 통해서 MemberService 에서 스탬프 업데이트를 위한 트랜잭션이 하나 더 시작된다.
![](https://blog.kakaocdn.net/dn/lc6Q7/btrQpJukkB1/U4Qr5ZFOJP49skfEjuMMwK/img.png)
두 개의 클래스에서 처리 되는 작업이 하나의 트랜잭션으로 묶인 모습이다.
그림을 보면 각각의 트랜잭션 경계선이 하나로 연결되어 있는 것을 볼 수 있다.
트랜잭션이 하나로 묶여있기 때문에 MemberService의 updateStamp() 메서드 작업을 처리하는 도중에 예외가 발생해도 두 클래스에서 작업을 처리하는 메서드들이 모두 하나의 트랜잭션 경계 내에 있으므로 모두 rollback 된다.
트랜잭션 전파(Transaction Propagation)
트랜잭션 전파란 트랜잭션의 경계에서 진행 중인 트랜잭션이 존재할 때 또는 존재하지 않을 때, 어떻게 동작할 것인지 결정하는 방식을 의미한다. 트랜잭션 전파는 propagation 애트리뷰트를 통해 설정할 수 있고, 대표적으로 아래와 같은 propagation 유형을 사용할 수 있다.
- Propagation.REQUIRED
일반적으로 가장 많이 사용되는 propagation유형의 디폴드 값이다 진행중인 트랜잭션이 없으면 새로 시작하고, 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여한다. - Propagation.REQUIRES_NEW
이미 진행중인 트랜잭션과 무관하게 새로운 트랜잭션이 시작된다. 기존에 진행 중이던 트랜잭션은 새로 시작된 트랜잭션이 종료할 때까지 중지된다. - Propagation.MANDATORY
진행 중인 트랜잭션이 없으면 예외를 발생시킨다. - Propagation.*NOT_SUPPORTED*
트랜잭션을 필요로 하지 않음을 의미. 진행중인 트랜잭션이 있으면 메서드 실행이 종료될 때까지 진행 중인 트랜잭션은 중지되며, 메서드 실행이 종료되면 트랜잭션을 계속 진행한다. - Propagation.*NEVER*
트랜잭션을 필요로 하지 않음음을 의미. 진행 중인 트랜잭션이 존재할 경우에는 예외를 발생시킨다.
이처럼 Spring에서는 다양한 Propagation 유형을 지원하지만 작업별로 트랜잭션을 새로 생성해야 한다거나 특정 작업에는 트랜잭션을 적용하지 않는 등의 경우가 아니라면 @Transactional 애너테이션만 추가해도 무방하다
트랜잭션 격리 레벨(Isolation Level)
ACID 원칙에서 살펴보았다시피 트랜잭션은 다른 트랜잭션에 영향을 주지 않고, 독립적으로 실행되어야 하는 격리성이 보장되어야 하는데 Spring은 이러한 격리성을 조정할 수 있는 옵션을 @Transactional 애너테이션의 isolation 애트리뷰트를 통해 제공하고 있다.
- Isolation.DEFAULT
데이터베이스에서 제공하는 기본 값 - Isolation.READ_UNCOMMITTED
다른 트랜잭션에서 커밋하지 않은 데이터를 읽는 것을 허용한다 - Isolation.READ_COMMITTED
다른 트랜잭션에 의해 커밋된 데이터를 읽는 것을 허용한다 - Isolation.REPEATABLE_READ
트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 한다 - Isolation.SERIALIZABLE
동일한 데이터에 대해서 동시에 두 개 이상의 트랜잭션이 수행되지 못하게 한다.
AOP 방식의 트랜잭션 적용
Spring에서 대부분의 트랜잭션 적용은 @Transactional 애너테이션을 사용하는 방법만으로 간단하게 적용이 가능하다
AOP를 이용하면 @Transactional 애너테이션 조차도 비즈니스 로직에 적용하지 않고 트랜잭션을 적용할 수 있다
AOP방식으로 트랜잭션을 적용하는 순서
- AOP방식으로 트랜잭션을 적용하기 위한 Configuration 클래스 정의
@Configuration 애너테이션을 추가하며 Configuration 클래스를 정의한다 - TransactionManager DI
애플리케이션에 트랜잭션을 적용하기 위해서는 TransactionManager 객체가 필요하다 - 트랜잭션 어드바이스용 TransactionInterceptor 빈 등록
Spring에서는 TransactionInterceptor를 이용해서 대상 클래스 또는 인터페이스에 트랜잭션 경계를 설정하고 트랜잭션을 적용할 수 있다.
-트랜잭션을 적용할 메서드에 트랜잭션 애트리뷰트 매핑
설정한 트랜잭션 애트리뷰트는 Map에 추가하는데, Map의 key를 메서드 이름 패턴으로 지정해서 각각의 트랜잭션 애트리뷰트를 추가해주면 된다.
트랜잭션 애트리뷰트를 추가한 Map 객체를 txAttributeSource.setNameMap(txMethods)으로 넘겨준다
-TransactionInterceptor 객체 생성
TransactionInterceptor의 생성자 파라미터로 transactionMabager와 txAttributeSource를 전달한다 - Advisor 빈 등록
-포인트 컷 지정
이제 트랜잭션 어드바이스인 TransactionInterceptor를 타깃 클래스에 적용하기 위해 포인트 컷을 지정한다
AspectJExpressionPointcut 객체를 생성한 후, 포인트 컷 표현식으로 CoffeeService클래스를 타깃 클래스로 지정한다
-Advisor 객체 생성
마지막으로 DefaultPointcutAdvisor의 생성자 파라미터로 포인트 컷과 어드바이스를 전달해준다
Point
- 트랜잭션 관련 설정은 Spring Boot이 내부적으로 알아서 해주기 때문에 개발자가 직접적으로 트랜잭션 설정해줄 필요가 없다.
- Spring에서는 일반적으로 애너테이션 방식( @Transactional )의 트랜잭션과 AOP 방식의 트랜잭션 적용 방식을 사용한다.
- 체크 예외(checked exception)는 @Transactional 애너테이션만 추가해서는 rollback이 되지 않으며, @Transactional(rollbackFor = {SQLException.class, DataFormatException.class})와 같이 해당 체크 예외를 직접 지정해주거나 언체크 예외(unchecked exception)로 감싸야 rollback 기능을 적용할 수 있다.
- 트랜잭션 전파란 트랜잭션의 경계에서 진행 중인 트랜잭션이 존재할 때 또는 존재하지 않을 때, 어떻게 동작할 것인지 결정하는 방식을 의미한다.
- @Transactional 애너테이션의 isolation 애트리뷰트를 통해 트랜잭션 격리 레벨을 지정할 수 있다.
'TIL' 카테고리의 다른 글
[Spring MVC][Testing] (0) | 2022.11.08 |
---|---|
[Spring][JPA][EntityMapping][JPA 연관 관계] (0) | 2022.11.07 |
[Spring MVC][DTO] (0) | 2022.11.03 |
[Java][Interface] (1) | 2022.11.02 |
[Spring][JPA][EntityMapping] (0) | 2022.11.01 |