Spring 03 - 트랜잭션(Transaction)

Posted by RoadtoS7 on May 28, 2020 · 9 mins read

3. 트랜잭션

  • 여러개의 작업을 하나로 묶은 것

  • 롤백 = 묶은 작업 중 하나라도 잘못되면, 작업이 실행되기 이전의 상태로 돌립니다.

3.1 트랜잭션 사용 이유

  • 데이터베이스를 수정하는 작업에서는 반드시 사용해야 합니다.
    b) 왜냐하면 데이터 베이스를 수정하다가 오류가 나면 데이터 베이스에 잘 못된 데이터가 들어갈 수 있습니다. 이 때 롤백기능을 사용해서 잘못된 데이터가 추가되지 않도록 만들어야 합니다.

  • 한번에 이루어져야 하는 작업들을 관리할 때 사용합니다.
    예시

    @Transactional
    public void Service(){
    minusPoint();
    sendPoint();
    plusPoint();
    }
    

위 작업에서 포인트를 주고받습니다. 만약 포인트를 다른 사람엑 전송하기 위해 minuPoint()를 실행한 후 오류가 난다면, 포인트는 전송되지 않고 사라집니다.
따라서 포인트를 주고 받는 행위에 포함되는 모든 작업을 트랜잭션으로 묶어서 작업 도중에 오류가 난다면 롤백이 일어날 수 있도록 해야합니다.

3.2 트랜잭션 성질

  1. 원자성
    • 한 트랜잭션 내에서의 작업은 모두 하나로 간주합니다.
    • 따라서 트랜잭션에 속하는 작업들의 실행 결과는 모두 성공하거나 모두 실패하거나 둘 중 하나입니다.
  2. 일관성
    • 트랜잭션은 일관성있는 데이터베이스 상태를 유지합니다.
      (data integrity 만족)
  3. 격리성
    • 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야 합니다.
  4. 지속성
    • 트랜잭션을 성공적으로 마치면 결과가 항상 저장되어야 합니다.

3.2 트랜잭션 설정 방법

  • 스프링에서는 트랜잭션 처리를 다양한 방법으로 지원합니다.
    • xml 방식
    • JavaConfig 방식
    • 어노테이션(annotaion) 방식
  • 그중 어노테이션 방식으로 @Transactional 을 선언하여 사용하는 방법이 일반적이며, 이 방식으로 생성된 트랜잭션을 선언적 트랜잭션이라고 부릅니다.

3.3 @Transactional 을 이용한 선언적 트랜잭션

  • 클래스, 메서드 코드 위에 @Transactional 어노테이션이 추가되면 해당 클래스에 프록시 기능이 적용된 프록시 객체가 생성됩니다.

  • 이 프록시 객체는 @Transacitonal 이 포함된 메서드가 호출되었을 때, PlatformTransactionalManager 를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라서 Commit 또는 Rollback을 합니다.

3.4 여러개의 트랜잭션이 경쟁할 때 발생할 수 있는 문제

  1. Dirty Read
    • 트랜잭션 A가 어떤 값을 1에서 2로 변경하고 아직 커밋하지 않은 상황에서 트랜잭션 B가 같은 값을 읽는다면, 트랜잭션 B는 해당 값을 2로 받아옵니다.
  • 하지만 트랜잭션 B가 값을 읽어간 후, 트랜잭션 A가 롤백된다면 트랜잭션 B는 잘못된 값을 가져가게 된 것입니다.
  • 트랜잭션이 완료(commit)되지 않은 상황에서 데이터에 접근을 허용할 때 발생할 수 있는 데이터 불일치입니다.
  1. Non-Repeatable Read
    • 트랜잭션 A가 어떤 값을 읽은 후, 한번 더 읽는 작업을 한다고 가정합니다.
  • 만약 첫번째 읽기 작업에서 해당 값을 1로 읽어온 후, 트랜잭션 B가 해당 값을 1에서 2로 바꾼 후 커밋해버린다면, A는 첫번째 읽기 작업을 통해서 얻어온 값과 두번재 읽기 작업을 통해서 얻어 온 값이 달라집니다.
  • 한 트랙잭션에서 똑같은 쿼리를 두번 실행했을 때 발생할 수 있는 데이터 불일치입니다.
  • Dirty Read 보다는 일어날 확률이 적습니다.
  1. Phantom Read
    • 트랜잭션 A가 어떤 쿼리를 사용하여 특정 범위의 값[0, 1, 2, 3, 4] 를 읽었다고 가정합니다.
  • 이후 A는 똑같은 쿼리를 한번더 실행할 것인데, 그 사이에 트랜잭션 B가 앞서 [0, 1, 2, 3, 4] 가 들어있던 테이블에 값 [5, 6, 7]을 추가한다면, A가 이후 똑같은 쿼리를 실행했는데도 쿼리의 결과로 반환되는 값이 달라집니다.
  • 한 트랜잭션에서 일정 범위의 레코드를 두번이상 읽을 때 나타나는 데이터 불일치입니다.

3.5 트랜잭션 속성

  • 스프링에서는 위와 같은 트랙재션 경쟁을 방지할 수 있는 속성을 지원합니다.
  1. isonlation(격리수준)
    • 트랜잭션에서 일관성이 없는 데이터를 허용하는 수준을 말합니다.
  2. DEFAULT = 기본 격리 수준(기본 설정, DB의 isolation level을 따릅니다.)
  3. READ_UNCOMMITED (level 0) = 커밋되지 않은(트랜잭션이 처리중인) 데이터에 대해서 읽기 허용 - Dirty Read 발생 가능

  4. READ_COMMITED ( level 1) = 트랜잭션이 커밋한 확정 데이터만 읽기 허용 - Dirty Read 발생하지 않습니다.

  5. REPEATABLE_READ( level 2) = 트랜잭션이 완료될 때까지 해당 트랜잭션의 SELECT 문장에서 사용하는 모든 데이터에 shared lock 이 걸리기 때문에, 다른 사용자는 해당 데이터에 대한 수정이 불가능합니다.
    - 한 트랜잭션이 읽은 데이터는 다른 트랜잭션이 수정, 삭제가 불가능하기 때문에 같은 쿼리를 두번 하더라도 똑같은 결과를 얻을 수 있습니다. - Non-Repeatable Read 발생하지 않습니다.

  6. SERIALIABLE( level 3) = 데이터의 일관성 및 동시성을 위해서 MVCC(Multi Vision Concurrency Control) 을 사용하지 않습니다.
    - 트랜잭션이 완료될 때까지, 해당 트랜잭션의 SElECT 문에서 사용하는 모든 데이터에 대해 shared lock 이 걸립니다. 따라서 다른 사용자는 해당 영역에 대항하는 데이터에 대해 수정, 입력이 불가능합니다.
    - Phantom Read 발생하지 않습니다.
  • 격리 수준이 올라갈 수록 성능 저하의 우려가 있습니다.
  • 예시 ``` @Transactional(isolation = Isolation.DEFAULT) public void somthing(int a){ … }

2. propagation (전파 옵션)
- 한 트랜잭션에서 다른 트랜잭션을 호출하는 상황에서 선택할 수 있는 옵션

  1. REQUIRED = 디폴트
    - 부모 트랜잭션 내에서 실행하며, 부모 트랜잭션이 없는 경우 새로운 트랜잭션을 생성합니다.
    
  2. SUPPORTS
    - 이미 시작된 트랜잭션이 있으면 참여하고 그렇지 않으면 트랜잭션없이 진행되게 합니다.
    
  3. REQUIRES_NEW
    - 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성됩니다.
    - 이미 진행중인 트랜잭션이 있으면, 트랜잭션 생성을 잠시 보류시킵니다.  
    
  4. MANDATORY
    - REQUIRED 와 비슷하게, 이미 시작된 트랜잭션이 있으면 참여합니다.
    - 이미 시작된 트랜잭션이 없으면, 새로 시작하는 대신 예외를 발생시킵니다.
    - 혼자서 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용합니다.
    
  5. NOT_SUPPORTED
    - 트랜잭션을 사용하지 않도록 강제합니다.
    - 이미 실행중인 트랜잭션이 존재하면 예외를 보류시킨다.
    
  6. NEVER
    - 트랜잭션을 사용하지 않게 한다.
    - 이미 진행 중인 트랜잭션이 있으면 예외를 발생시킨다.
    
  6. NESTED
    - 이미 진행중인 트랜잭션이 있으면, 중첩 트랜잭션을 시작합니다.
    - 중첩 트랜잭션 = 트랜잭션안에 트랜잭션을 만드는 것
    - 중첩 트랜잭션은 먼저 시작된 트랜잭션(부모 트랜잭션)의 커밋과 롤백에는 영향을 받지만 자신의 커밋, 롤백은 부모 트랜잭션에 영향을 주지 않습니다.
    
    - EX. 쇼핑몰에서 주문트랜잭션에 로그를 남기는 작업이 있는 경우
    - 이 로그를 남기는 작업이 실패한다고 해서 메인 작업까지 롤백되면 안됩니다.  
    - 하지만 주문작업이 실패했을 때는 로그를 남기지 않는 것이 맞습니다.  
    - 이 때 중첩 트랜잭션을 사용하면 됩니다.  
  
3. readOnly 속성
- 트랜잭션을 읽기전용 속성으로 지정할 수 있습니다.  

- 성능 최적화를 위해서 or 특정 트랜잭션 작업안에서 쓰기 작업이 일어나는 것을 막기 위해서 사용할 수 있습니다.  
- 일부 트랜잭션의 경우 일기전용 속성을 무시하고 쓰기 작업을 허용할 수도 있습니다.  
- readOnly 속성이 지정된 트랜잭션에서 INSERT, DELETE, UPDATE 작업과 같이 쓰기 작업이 일어나면 예외가 발생합니다.  
- aop/tx 스키마로 트랜잭션 선언할 때는 이름 패턴을 이용해서 읽기 전용 속성으로 만드는 경우가 많다. 보통 get이나 find같은 이름의 메소드를 읽기 전용 메소드로 만들어서 사용하면 편합니다.  
- `@Transactional`을 사용하여 트랜잭션을 만드는 경우는 일일이 읽기 전용 속성을 지정해줘야 합니다.  `read-only` 속성 또는 `readOnly` 속성을 true로 지정하면 읽기 전용으로 지정됩니다.  
- true인 경우에는 INSERT, UPDATE, DELETE 발생시 예외 발생하며, 디폴트는 false로 되어  있습니다.  
- 예시

@Transaction(readOnly = true)


4. 트랜잭션 롤백 예외 (rollback-for, rollbackFor, rollbackForClassName)
- 선언적 트랜잭션에서는 런타임에서 예외(오류)가 발생하면 롤백합니다.  

- 반면에 예외가 전여 발생하지 않거나 체크 예외가 발생하면 커밋합니다.  
- 체크 예외가 커밋 대상인 이유 = 체크 예외가 예외적인 상황일 때 발생하기 보다는 리턴 값을 대신해서 비즈니스적인 의미를 담은 결과를 반환하는 용도로 사용되기 때문입니다.
- 스프링에서는 데이터 액세스 때 발생하는 예외는 런타임 예외로 발생하기 때문에, 런타임 예외가 일어났을 때에만 롤백이 일어납니다.  
- 하지만 롤백이 일어나는 대상을 바꿀 수 있습니다.  
ex. 체크 예외이지만 롤백이 일어나야하는 경우가 있다면, XML의 roolback-for 애트리뷰트나 어노테이션 `@rollback` 혹은 rollbackForClassName 속성을 사용해서 예외를 지정하면 됩니다.  
rollback-for 애트리뷰트 나 rollbackForClassName 속성에는 예외 이름을 지정하면되고, `@rollbackFor`는 예외 대상 클래스에 넣어주면됩니다.  
- `@Transaction` 어노테이션에서 `rollbackFor` 속성을 이용하여 지정하는 경우에는 클래스 이름 대신 클래스를 직접 사용해도 됩니다.  
ex.

@Transactional(readOnly = true, rallbackFor = NoSuchMemberException.class) ```

  • @Transactional 어노테이션에서 롤백 관련 속성
  • rollbackFor: 특정 예외가 발생했을 때 강제로 Rollback
    예: @Transactional(rollbackFor = Exception.class)
  • noRollbackFor: 특정 예외 발생시 Rollback 하지 않음 예: @Transaction(noRollbackFor = Exception.class)
  1. timeout 속성
    • timeout 속성으로 지정한 시간내에 해당 트랜잭션이 완료되지 않은 경우 rollback 수행합니다.
  • -1이일 경우 timeout 속성을 지정하지 않은 것이며, default에 해당합니다.
  • ex) Transactional(timeout = 10)