본문 바로가기
스프링

스프링에서 트랜잭션을 처리하는 방법: @Transactional

by 책 읽는 개발자_테드 2022. 2. 4.
반응형

JPA와 트랜잭션 관리

· JPA 자체는 어떠한 선언적인 형식의 트랜잭션 관리 기능도 제공하지 않는다. DI 컨테이너 외부에서 JPA를 사용할 때 트랜잭션은 개발자가 프로그래밍 방식으로 처리해야 한다.

- 선언형 프로그래밍은 '무엇을'을 정의하믄 프로그래밍, 명령형은 프로그래밍은 '어떻게'를 정의하는 프로그래밍을 말한다. https://velog.io/@mgm-dev/선언적-VS-명령적

 

UserTransaction utx = entityManager.getTransaction(); 

try { 
    utx.begin(); 
    businessLogic();
    utx.commit(); 
} catch(Exception ex) { 
    utx.rollback(); 
    throw ex; 
} 

 

· 이러한 트랜잭션 관리 방식은 코드에서 트랜잭션 범위를 매우 명확하게 정의하지만, 몇 가지 단점이 있다.

1. 반복적이고, 오류가 발생하기 쉽다.

2. 모든 오류는 매우 큰 영향을 미칠 수 있다.

3. 오류는 디버그 및 재현하기 어렵다.

4. 코드의 가독성을 감소시킨다.

 

스프링 @Transactional 애너테이션 사용하기

· 스프링 @Transactional을 사용하면, 위 코드를 아래와 같이 간단하게 표현할 수 있다.

즉, 스프링은 @Transactional 애노테이션을 이용해 선언적 트랜잭션 처리를 제공한다.

@Transactional
public void businessLogic() {
... use entity manager inside a transaction ...
}

· 해당 방식은 더 편리하고, 읽기 쉽고, 현재 스프링에서 트랜잭션을 처리하는 데 권장되는 방법이다.

· @Transactional을 사용하면 트랜잭션 전파와 같은 많은 중요한 요소가 자동으로 처리된다. 이 경우 다른 트랜잭션 메서드가 businessLoginc()에 의해 호출되면 해당 메서드는 진행중인 트랜잭션에 참여하여된다.

· 하지만, 내부에서 일어나는 일을 숨겨서 작동하지 않을 때 디버그하기 어렵게 만드는 단점이 있다.

 

트랜잭션이란?

데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위를 말하며, 논리적인 작업 셋 자체가 100% 적용되거나, 아무것도 적용되지 않아야함을 보장해 주는 것이다. 자세한 내용: https://scshim.tistory.com/471

 

@Transactional의 옵션

· @Transactional 애너테이션은 트랜잭션 처리를 위한 다양한 옵션을 제공한다.

 

isolation

· 트랜잭션의 격리 수준을 정한다.
여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것이다. 자세한 내용: https://scshim.tistory.com/471

 

· 적용방법: @Transactional(isolation=Isolation.DEFAULT)

DEFAULT 데이터 베이스의 기본 격리 수준을 따른다.
READ_UNCOMMITED 커밋되지 않은 데이터에 대한 읽기 허용
READ_COMMITED 커밋된 데이터에 대한 읽기 허용
REPEATEABLE_READ 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장한다. (MySQL에서는 MVCC를 위해 언두 영역에 백업된 이전 데이터를 이용한다.)

- MVCC: MySQL InnoDB 스토리지 엔진에서 트랜잭션이 ROLLBACK될 가능성에 대비해 변경되기전 레코드를 언두 공간에 백업해두고 실제 레코드 값을 변경하는 기술이다.
SERIALIZABLE 가장 단순하지만, 가장 엄격한 격리 수준이다. SERIALIZABLE 격리 수준에서는 읽기 작업도 공유 잠금(읽기 잠금)을 획득해야 하며, 동시에 다른 트랜잭션은 해당 레코드를 변경하지 못한다.

 

propagation

· 트랜잭션 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정한다. (전파옵션)

· 적용방법: @Transactional(propagation=Propagation.REQUIRED)

REQUIRED (Defualt)  이미 진행중인 트랜잭션이 있다먄 해당 트랜잭션 속성을 따르고, 진행중이 아니라면 새로운 트랜잭션을 생성한다.
REQUIRES_NEW 항상 새로운 트랜잭션을 생성한다. 이미 진행준인 트랜잭션이 있다면 잠깐 보류하고 해당 트랜잭션 작업을 먼저 진행한다.
SUPPORT 이미 진행 중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 없다면 트랜잭션을 설정하지 않는다.
NOT_SUPPORT  이미 진행중인 트랜잭션이 있다면 보류하고, 트랜잭션 없이 작업을 수행한다.
MANDATORY 이미 진행중인 트랜잭션이 있어야만 작업을 수행한다. 없다면 예외를 발생시킨다.
NEVER 트랜잭션이 진행중이지 않을 때 작업을 수행한다. 트랜잭션이 있다면 Exception을 발생시킨다.
NESTED 진행중인 트랜잭션이 있다면 중첩된 트랜잭션이 실행되며, 존재하지 않으면 REQUIRED와 동일하게 실행된다.

 

noRollbackFor

· 특정 예외 발생시, 롤백되지 않는다.

· 적용방법: @Transactional(noRollbackFor=Exception.class)

 

rollbackFor

· 특정 예외 발생시, 롤백된다.

· @Transaction을 선언하면 Default 설정으로, UncheckedException, Error에 대해서 Rollback을 시킨다.

· 적용방법: @Transactional(rollbackFor=Exception.class)

 

timeout

· 지정한 시간 내에 해당 작업이 완료되지 않을 경우 rollback을 수행한다. (-1로 설정하면, timeout을 사용하지 않는다.)

· 적용방법: @Transactional(rollbackFor=Exception.class)

 

readOnly

· 트랜잭션을 읽기 전용으로 설정한다.

- Default 값을 false이고, true 설정시 insert, update, delete를 실행하면 예외가 발생한다.

· 적용방법: @Transactional(readonly=true)

 

테스트 환경에서 @Transactional 동작

· 스프링은 테스트에 사용되는 애플리케이션 켄텍스트를 생성하고, 관리하여 테스트에 적용해주는 테스트 프레임워크인 Test Context Framework를 제공한다. https://mangkyu.tistory.com/202

 

스프링의 테스트 코드에서 @Transactional 애너테이션을 사용하면, 테스트 종료시 자동으로 롤백이된다.

 

주의사항!

1. WebEnvironment의 RANDOM_PORT, DEFINED_PORT 옵션을 사용하면 실제 테스트 서버는 별도의 스레드에서 테스트를 수행하기 때문에 트랜잭션이 롤백되지 않는다.

스프링 공식 문서

 

2. 트랜잭션에 포함된 insert 작업으로 인해 증가한 id는 트랜잭션이 롤백되어도 다시 감소하지 않는다.

 

출처

https://jaeseongdev.github.io/development/2021/05/11/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98_Transactional/

https://dzone.com/articles/how-does-spring-transactional

https://tecoble.techcourse.co.kr/post/2021-05-25-transactional/

https://velog.io/@kdhyo/JavaTransactional-Annotation-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-26her30h

반응형

댓글