Spring

[Spring] Transactional 어노테이션 이해 및 사용하기

별토끼. 2021. 8. 3. 23:36
반응형

다양한 옵션들을 잘 모르고 @Transactional 을 사용하다 보니 다양한 상황을 고려하지 못하고 개발하는 경우가 있다. 무작정 사용하지 말고, 이해하고 사용하도록 하자!

트랜잭션

비즈니스 로직에서 쪼개질 수 없는 하나의 작업 단위.

원자성(Atomicity)

한 트랜잭션 내에서 실행한 작업들은 하나로 간주. (모두 성공 or 모두 실패)

일관성(Consistency)

트랜잭션은 일관성 있는 데이터베이스 상태를 유지.

격리성(Isolation)

동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않게 격리.

영속성(Durability)

트랜잭션을 성공적으로 마치면, 결과가 항상 영속적으로 보관되어야 함.

Spring에서 트랜잭션 처리

보통 @Transactional 어노테이션을 선언하여 사용한다.

동작방식

@Transactional 어노테이션은 AOP를 사용하여 구현이 되는데, transaction의 begin과 commit을 메인 로직 앞 뒤로 수행해주는 기능을 담당한다.

 

@Service
@Slf4j
public class ProductService {

    private final SchoolDao schoolDao;

    @Transactional
    public void insertProductInfo(SchoolDto schoolDto) {
        schoolDao.insertProduct(schoolDto);

        for(Student student:SchoolDto.getStudent()) {
            schoolDao.insertStudentInfo(student);
        }
    }
}

 

insertProductInfo 메서드에@Transactional 어노테이션이 있다. @Transactional 포함된 메서드가 호출되면, PlatformTransactionalManager를 사용하여 트랜잭션을 시작한다. 메서드가 실행하기 전 begin을 호출하고, 메서드가 종료된 뒤 commit을 호출한다. 비정상일 경우, Rollback한다.

 

 

Transactional에 적용된 AOP는 Proxy 패턴을 사용하여 구현이 되는데, 검색을 하다보니 흥미로운 포스팅(https://minkukjo.github.io/framework/2021/05/23/Spring/   )이 있었다. 한 번 읽어보면 좋을 듯 하다.

다수의 트랜잭션 경쟁 경우

특정 트랜잭션이 처리중이고, 아직 커밋되지 않았는데 다른 트랜잭션이 접근한 경우 문제가 발생할 수 있다.

  1. Dirty Read
    1. 트랜잭션 A가 1 -> 2로 변경한 상황. commit은 안됨
    2. 트랜잭션 B가 같은 값을 읽음. B가 조회한 결과값은 2
    3. B가 2를 조회한 후, A가 rollback하면 B는 값을 잘못 읽어온 것이 됨
    4. 가장 빈도 높은 경쟁 경우
  2. Non-Repeatable Read
    1. 트랜잭션 A가 어떤 값 1을 읽음, 곧 같은 쿼리를 또 수행할 예정
    2. 갑자기 트랜잭션 B가 1을 2로 수정
    3. 트랜잭션 A가 같은 쿼리를 수행했는데, 1이 아닌 2를 가져옴
  3. Phantom Read
    1. 트랜잭션 A가 어떤 조건 사용하여 특정 범위 값(0,1,2,3)들을 읽어옴
    2. 갑자기 트랜잭션 B가 값을 추가(4,5)
    3. A가 동일한 쿼리 수행 시 다른 결과값을 가져오게 됨

어떻게 해결할까

이러한 경쟁 상황을 방지할 수 있는 속성들이 있다. 이를 잘 알고 활용하면 장애를 방지할 수 있다!

  1. 격리 수준(isolation)
  2. 전파 옵션(propagation)
  3. readOnly 속성
  4. 트랜잭션 롤백 예외

격리수준

일관성 없는 데이터를 허용하도록 하는 수준. 어노테이션 옵션으로 설정이 가능하다.

  1. DEFAULT
  2. READ_UNCOMMITTED (level 0)
  3. READ_COMMITTED (level 1)
  4. REPEATABLE_READ (level 2)
  5. SERIALIZABLE (level 3)

-예시

@Transactional(isolation=Isolation.DEFAULT) public void something (int a) {
    ...
}

전파옵션

트랜잭션 동작 도중 다른 트랜잭션을 호출하는 상황에서 선택할 수 있는 옵션

@Transactional의 propagation 속성을 통해서 새롭게 트랜잭션을 생성하거나, 피호출 트랜잭션이 호출한 쪽의 트랜잭션을 그대로 사용할 수도 있다.

readOnly 속성

트랜잭션을 읽기 전용으로 설정할 수 있다.
성능 최적화나 특정 트랜잭션 작업 안의 쓰기 작업을 방지하기 위해 사용하기도 한다. 만약 insert, delete, update 같은 쓰기 작업이 일어나면 exception 처리된다.

-예시

@Transactional(readOnly = true)

트랜잭션 롤백 예외

런타임 예외가 발생하면 롤백한다.하지만 기본 동작 방식을 바꾸어 특정 예외 발생 시 rollback 처리하지 않도록 하거나, rollback 처리하도록 할 수 있다.
-예시

@Transactional(rollbackFor=Exception.class)
@Transactional(noRollbackFor=Exception.class)

timeout 속성

지정한 시간 내 메서드 수행이 완료되지 않으면 rollback을 수행하도록 한다. timeout 값을 -1로 주면 no timeout. (default = 1)

@Transactional(timeout=10)

 

 

참고
https://goddaehee.tistory.com/167
https://minkukjo.github.io/framework/2021/05/23/Spring/

반응형