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://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
'스프링' 카테고리의 다른 글
스프링 세션 (Spring Session)이란? spring-session-jdbc 사용하기 (0) | 2021.11.26 |
---|---|
[Spring] 스프링에서 AOP를 구현하는 방법 (0) | 2021.11.10 |
스프링에서 버전을 일관성있게 다루는 방법: BOM (0) | 2021.10.26 |
[Spring Boot] 스프링 부트 그레이들 플러그인(Gradle Plugin) (0) | 2021.09.03 |
[F-lab] 4주차 정리_자바 (0) | 2021.09.03 |
댓글