✅ Transactional
스프링 환경에서는 @Transactional을 이용하여 메서드, 클래스, 인터페이스의 트랜잭션 처리가 가능한데 이러한 방식을 선언적 트랜잭션이라 부른다. 내부적으로 좀 뜯어보면 트랜잭션 기능이 들어가 있는 프록시 객체가 생성되어 자동적으로 커밋이나 롤백을 해준다고 생각하면 된다.
간단한 예시로 MemberService에서 MemberDTO 클래스를 insert를 하는 Service 내부의 메서드를 만든다고 가정해보면 아래처럼 구현을 할 수가 있다.
@Service
public class MemberService {
```
@Transactional
public void addMember(MemberDto memberDto) throws Exception {
// 멤버 삽입 로직 구현
}
```
}
✅ Transactional 옵션
🔍 isolation
- isolation 옵션을 트랜잭션에서 일관성없는 데이터를 어떻게 허용할지에 대한 허용 수준을 정할수 있는 옵션
아래와 같은 형태로 설정을 해주면 된다.
@Service
public class MemberService {
```
@Transactional(isolation=Isolation.DEFAULT)
public void addMember(MemberDto memberDto) throws Exception {
// 멤버 삽입 로직 구현
}
```
- DEFAULT
- 기본적인 격리 수준이고, 기존 DB의 Isolation Level을 따르게 된다.
- READ_UNCOMMITED (Level 0)
- 커밋되지 않는 데이터에 대한 읽기를 허용하는 방식이다.
이렇게 설정하면 Dirty Read라는 문제가 발생할 수 있다.
예를 들어 사용자 A가 1이라는 데이터를 2로 변경한다고 했을때, 사용자 B가 아직 완료되지 않은 (Uncommitted or Dirty) 데이터를 읽을 수 있는데, 만약 사용자가 A 가 수행한 데이터가 정상커밋되지 않아 롤백될 경우 사용자 B가 읽은 데이터는 잘못된 데이터가 되는 것이다. - READ_COMMITED (Level 1)
- 커밋된 데이터에 대해 읽기 를 허용하는 방식이다. 즉, 사특정 사용자가 데이터를 변경하는 동안 다른 사용자는 해당 데이터에 접근이 불가하다.
이렇게 하면 Dirty Read 문제는 방지할 수 있으나, Non-Repeatable Read 문제가 생긴다.
예를들어 사용자 A 가 1번 데이터를 조회하고 있을 때 사용자 B가 1번 데이터를 수정하고 커밋을 해버리면, 사용자 A가 같은 트랜잭션에서 다시 1번 데이터를 조회한다면 수정된 데이터가 조회되어 버려서 반복해서 데이터를 읽을 수 없는 경우를 의미한다. - REPEATABLE_READ (Level 2)
- 동일 필드에 대해 다중으로 접근할 때 동일한 결과를 보장하는 방식을 의미한다. 즉, 트랜잭션이 완료될 때까지 SELECT 문장이 사용되는 모든 데이터들에 대해 Shared Lock이 걸려 다른 사용자가 그 데이터에 대한 접근이 불가능해진다.
그러므로 어떤 트랜잭션이 수행될 때 다른 트랜잭션이 앞선 트랜잭션이 사용 중인 데이터에 대해 갱신하거나 삭제하는게 불가능하기에 트랜잭션 내에서 여러번 데이터를 접근한다해도 데이터의 일관성을 보장할 수 있다.
이렇게 하면 Non-Repeatable Read 문제는 방지할 수 있으나, Phantom Read 문제가 생긴다.
예를들어 사용자 A 가 특정 범위의 데이터 [1,2,3,4] 를 2번 읽는 트랜잭션이 수행된다고 가정하자. 두번째 읽기전에 사용자 B가 수행한 트랜잭션이 [5,6,7,8] 이라는 데이터를 추가해 버리면 첫번째 쿼리와 두번째 쿼리의 결과가 달라진다.
즉, 한 트랜잭션에서 일정한 범위의 레코드를 두번 이상 읽을 때, 데이터가 불일치하는 문제가 바로 Phantom Read 문제이다. - SERIALIZABLE (Level 3)
- 해당 설정은 데이터의 일관성과 동시성을 유지하기 위해 MVCC(Multi Version Concurrency Control) 을 사용 하지 않는다. (MVCC 는 다중 사용자 데이터 베이스 성능 을 위한 기술로 데이터를 조회할때 LOCK 을사용하지 않고 데이터의 버전을 관리하여 데이터 일관성과 동시성을 높이는 기술을 의미)
그리고 트랜잭션이 완료될 때 까지 SELECT 문장이 사용하는 모든 데이터에 Shared Lock 을 걸어 다른 사용자가 해당 영역에 있는 모든 데이터에 대한 수정과 입력이 불가능하게 만들어 Phantom Read를 방지한다.
설명만 보기에는 SERIALIZABLE 속성을 써야하겠지만 이렇게 격리수준이 높아지면 성능저하의 우려가 있으니 상황에 따라 적절한 속성을 사용해줘야한다.
🔍 propagation
- propagation 옵션은 트랜잭션이 동작할 때 다른 트랜잭션이 호출되면 어떻게 처리할지를 정하는 옵션이다.
즉 피호출 트랜잭션에서 호출한 트랜잭션을 그대로 사용할지 새로 생성할지를 정하는 옵션이라고 생각하면된다. 설정방법은 아래와 같이 적용해주면 된다.
@Service
public class MemberService {
```
@Transactional(propagation=Propagation.REQUIRED)
public void addMember(MemberDto memberDto) throws Exception {
// 멤버 삽입 로직 구현
}
```
}
- REQUIRED
- 디폴트인 속성이고, 부모 트랜잭션 내에서 실행하게 하고 만약 부모 트랜잭션이 없으면 새로운 트랜잭션을 생성하게 하는 설정이다. - SUPPORTS
- 이미 시작된 트랜잭션이 있으면 그 트랜잭션에 참여하여 처리하게 하고, 없으면 트랜잭션없이 진행하게 하는 설정이다. - REQUIRES_NEW
-부모 트랜잭션이 있어도 그냥 새롭게 트랜잭션을 생성하게 하는 설정이다. - MANDATORY
- REQUIRED 처럼 이미 시작한 트랜잭션에 참여하지만 , 없으면 새로 생성하는게 아니라 예외를 발생시킨다. 보통 독립적으로 트랜잭션이 진행되면 안되는 경우에 해당 옵션을 사용한다. - REQUIRES_NEW
- 항상 새로운 트랜잭션으로 작업을 수행한다.. 만악에 진행중인 트랜잭션이 있으면 트랜잭션을 잠시 보류시킨다. - NOT_SUPPORTED
- 트랜잭션 없이 작업을 수행한다. 만약 진행중인 트랜잭션이 있으면 트랜잭션을 잠시 보류시킨다. - NEVER
- 트랜잭션을 사용하지 않게 강제한다. 이미 진행중인 트랜잭션이 있다면 Exception을 발생시키고 트랜잭션이 진행중이지 않을때 작업을 수행한다. - NESTED
- 이미 진행중인 트랜잭션이 있으면 중첩된 트랜잭션을 실행하고 없으며 REQUIRED와 동일하게 새로운 트랜잭션을 생성하여 진행한다. 중첩 트랜잭션을 말 그대로 트랜잭션안에 트랜잭션을 만드는 것이다. 트랜잭션안의 트랜잭션이 커밋되거나 롤백되어도, 바깥의 트랜잭션에게 영향을 주지 않는다
🔍 noRollbackFor, .rollbackFor
- 특정 예외 발생 시, rollback을 하거나 하지않게 하는 옵션이다.
사용방법은 아래와 같다.
@Service
public class MemberService {
```
@Transactional(noRollvackFor=CustomException.class)
public void addMember(MemberDto memberDto) throws Exception {
// 멤버 삽입 로직 구현
}
```
}
선언적 트랜잭션에서는 런타임 예외가 발생하면 롤백을 하도록한다. 반면 체크 익셉션이 발생하면 커밋을 한다. 스프링에서는 Data Acces 기술의 예외가 런터임 예외로 전환되어 던져지기 때문에 런타임 예외만 롤백대상이 되지만 기본동작을 바꿀수 가 있는데 그 옵션이 바로 rollbackFor 속성이다.
그리고 반대로 특정 예외 발생시 Rollback 처리되지 않게 하는 옵션이 noRollbackFor 속성이다.
🔍 timeout
지정한 시간 내에 메서드 수행이 완료되지 않으면 rollback 하게 하는 옵션이다. -1 이 default 값이고 이는 no timeout을 의미한다.
사용 방법은 아래와 같다.
@Service
public class MemberService {
```
@Transactional(timeout=10)
public void addMember(MemberDto memberDto) throws Exception {
// 멤버 삽입 로직 구현
}
```
}
🔍 readOnly
트랜잭션을 읽기전용으로 설정하는 옵션이다. true로 설정하면 insert, update, delete 실행할 때 예외가 발생한다.
default 값은 false이다.
사용방법은 아래와 같다.
@Service
public class MemberService {
```
@Transactional(readOnly =true)
public List<Member> findAllMember() throws Exception {
// 전체 멤버 검색 로직 구현
}
```
}
보통 get 이나 find 같은 이름의 메서드 앞에 이런식으로 설정이 되어있다. 성능을 최적화 하기위해서도 사용할 수 있다고하지만 나는 보통 특정 트랜잭션 작업에서 누군가 쓰기 작업을 하는 걸 의도적으로 방지하기 위해 사용해왔다.
'Back-end > SpringBoot' 카테고리의 다른 글
| [ SpringBoot] AOP (4) | 2025.07.25 |
|---|---|
| [SpringBoot] @JsonIgnoreProperties(ignoreUnknown=true) (0) | 2025.05.30 |
| [SpringBoot] Object Mapper (1) | 2025.05.29 |
| [SpringBoot] REST API 구조 설계 및 SpringBoot 응답 클래스 만들기(feat. SingleResult, ListResult) (0) | 2024.12.19 |
| [Springboot] REST API 만들고 슬라이스 테스트하기 (feat. HAL Explorer 사용하기) (0) | 2024.12.02 |