본문 바로가기
책벌레와 벌레 그 사이 어딘가/개념쌓기

[개념쌓기] @Transactional

by veganwithbacon 2022. 8. 10.
반응형

야자반에서 @Transactional 에 관해 듣게 되다보니 모르는 내용도 많고, @Transactional이 작동하지 않는 경우도 알게되어 확실히 정리해두고자 블로그에 정리하게 되었다.

 

 

  @Transactional 

  - transaction을 사용하면 begin,commit을 자동 수행해준다

  - 예외를 발생시키면, rollback 처리를 자동 수행해준다

 

 

  Spring에서의 @Transactional 기능제공 방식

JPA의 객체 변경감지는 transaction이 commit될 때,작동한다

그래서 Spring은 @Transactional 어노테이션을 선언한 메소드가 실행되기전, transaction begin코드를 삽입하여

메소드가 실행된 후,transaction commit 코드를 삽입하여, 객체 변경감지를 수행하도록 유도한다.

 

 

Spring의 코드 삽입 방법은 크게 2가지가 있다

- 바이트 코드 생성(CGLIB 사용)

- 프록시 객체 사용

 

Spring에서는 기본적으로 프록시 객체 사용을 하기에 Interface가 반드시 필요하다.

SpringBoot에서는 기본적으로 바이트 코드 생성이 선택되기에,  굳이 interface가 필요없는 것이다

 

 

  @Transactional의 옵션

  1. isolation

    :  트랜잭션에서 일관성없는 데이터를 어떻게 허용할지에 대한 허용 수준을 정할수 있는 옵션

@Service
public class MemberService { 
``` 
	@Transactional(isolation=Isolation.DEFAULT) 
    	public void addMember(MemberDto memberDto) throws Exception { 
    	// 멤버 삽입 로직 구현 
     } 
``` 
}

1) DEFAULT

      기본적인 격리 수준이고, 기존 DB의 Isolation Level을 따르게 된다

 

2) READ_UNCOMMITED (Level 0)

커밋되지 않는 데이터에 대한 읽기를 허용하는 방식이다.
이렇게 설정하면 Dirty Read라는 문제가 발생할 수 있다.

예를 들어 사용자 A가 1이라는 데이터를 2로 변경한다고 했을때, 사용자 B가 아직 완료되지 않은 (Uncommitted or Dirty) 데이터를 읽을 수 있는데, 만약 사용자가 A 가 수행한 데이터가 정상커밋되지 않아 롤백될 경우 사용자 B가 읽은 데이터는 잘못된 데이터가 되는 것이다.

 

3) READ_COMMITED (Level 1)

  커밋된 데이터에 대해 읽기 를 허용하는 방식이다. 즉, 특정 사용자가 데이터를 변경하는 동안 다른 사용자는 해당 데이터에 접근이 불가하다.
이렇게 하면 Dirty Read 문제는 방지할 수 있으나, Non-Repeatable Read 문제가 생긴다.

예를들어 사용자 A 가 1번 데이터를 조회하고 있을 때 사용자 B가 1번 데이터를 수정하고 커밋을 해버리면, 사용자 A가 같은 트랜잭션에서 다시 1번 데이터를 조회한다면 수정된 데이터가 조회되어 버려서 반복해서 데이터를 읽을 수 없는 경우를 의미한다.

 

4) 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 문제다

 

5) SERIALIZABLE (Level 3)

해당 설정은 데이터의 일관성과 동시성을 유지하기 위해 MVCC(Multi Version Concurrency Control) 을 사용 하지 않는다. (MVCC 는 다중 사용자 데이터 베이스 성능 을 위한 기술로 데이터를 조회할때 LOCK 을사용하지 않고 데이터의 버전을 관리하여 데이터 일관성과 동시성을 높이는 기술을 의미)
그리고 트랜잭션이 완료될 때 까지 SELECT 문장이 사용하는 모든 데이터에 Shared Lock 을 걸어 다른 사용자가 해당 영역에 있는 모든 데이터에 대한 수정과 입력이 불가능하게 만들어 Phantom Read를 방지한다.

설명만 보기에는 SERIALIZABLE 속성을 써야하겠지만 이렇게 격리수준이 높아지면 성능저하의 우려가 있으니 상황에 따라 적절한 속성을 사용해줘야한다.

 

 

  2.propagation

    : 트랜잭션이 동작할 때 다른 트랜잭션이 호출되면 어떻게 처리할지를 정하는 옵션

     (피호출 트랜잭션에서 호출한 트랜잭션을 그대로 사용할지 새로 생성할지를 정하는 옵션)

@Service
public class MemberService { 
``` 
	@Transactional(propagation=Propagation.REQUIRED)
    public void addMember(MemberDto memberDto) throws Exception { 
    	// 멤버 삽입 로직 구현 
    } 
``` 
}

 

1) REQUIRED

디폴트 속성으로, 부모 트랜잭션 내에서 실행하게 하고 만약 부모 트랜잭션이 없으면 새로운 트랜잭션을 생성하게 한다

 

2) SUPPORTS

이미 시작된 트랜잭션이 있으면 그 트랜잭션에 참여하여 처리하게 하고, 없으면 트랜잭션없이 진행하게 하는 설정이다

 

3) REQUIRES_NEW

부모 트랜잭션이 있어도 그냥 새롭게 트랜잭션을 생성하게 하는 설정이다.

 

4) MANDATORY

REQUIRED 처럼 이미 시작한 트랜잭션에 참여하지만 , 없으면 새로 생성하는게 아니라 예외를 발생시킨다. 보통 독립적으로 트랜잭션이 진행되면 안되는 경우에 해당 옵션을 사용한다

 

5) REQUIRES_NEW

항상 새로운 트랜잭션으로 작업을 수행한다.. 만악에 진행중인 트랜잭션이 있으면 트랜잭션을 잠시 보류시킨다.

 

5) NOT_SUPPORTED

트랜잭션 없이 작업을 수행한다. 만약 진행중인 트랜잭션이 있으면 트랜잭션을 잠시 보류시킨다.

 

6) NEVER

트랜잭션을 사용하지 않게 강제한다. 이미 진행중인 트랜잭션이 있다면 Exception을 발생시키고 트랜잭션이 진행중이지 않을때 작업을 수행한다.

 

7) NESTED

이미 진행중인 트랜잭션이 있으면 중첩된 트랜잭션을 실행하고 없으며 REQUIRED와 동일하게 새로운 트랜잭션을 생성하여 진행한다. 중첩 트랜잭션을 말 그대로 트랜잭션안에 트랜잭션을 만드는 것이다. 트랜잭션안의 트랜잭션이 커밋되거나 롤백되어도, 바깥의 트랜잭션에게 영향을 주지 않는다.

 

  3.noRollbackFor, .rollbackFor

특정 예외 발생 시, rollback을 하거나 하지않게 하는 옵션이다.

@Service
public class MemberService { 
``` 
	@Transactional(noRollvackFor=CustomException.class) 
    public void addMember(MemberDto memberDto) throws Exception { 
    	// 멤버 삽입 로직 구현 
    }
``` 
}

 

스프링에서는 Data Acces 기술의 예외가 런터임 예외로 전환되어 던져지기 때문에 런타임 예외만 롤백대상이 되지만 기본동작을 바꿀수 가 있는데 그 옵션이 바로 rollbackFor 속성이다.

그리고 반대로 특정 예외 발생시 Rollback 처리되지 않게 하는 옵션이 noRollbackFor 속성이다.

 

  4. timeout

지정한 시간 내에 메서드 수행이 완료되지 않으면 rollback 하게 하는 옵션이다.

-1 이 default 값이고 이는 no timeout을 의미한다.

@Service
public class MemberService { 
``` 
	@Transactional(timeout=10)
    public void addMember(MemberDto memberDto) throws Exception { 
    	// 멤버 삽입 로직 구현 
    } 
```
}

 

  5. readOnly

트랜잭션을 읽기전용으로 설정하는 옵션이다. true로 설정하면 insert, update, delete 실행할 때 예외가 발생한다.
default 값은 false이다.

 

@Service
public class MemberService {
``` 
	@Transactional(readOnly =true)
    public List<Member> findAllMember() throws Exception { 
    	// 전체 멤버 검색 로직 구현 
      } 
``` 
}

보통 get 이나 find 같은 이름의 메서드 앞에 이런식으로 설정이 되어있다.

 

 

그래서 요약이 뭐냐?라고 물으시면

트랜잭션은 단위가 하나로 움직이며 중간에 잘못되면 멈추는데 로직안에서 잘못되면 처음으로 되돌린다

데이터베이스가 변경이 감지되면 Jpa가 바로 커밋을 날리며 데이터베이스에 적용되는 것을 커밋이라고 한다.

업데이트를 하면서 엔티티에서 수정 로직을 만들면 트랜잭션을 자동 적용해준다

위 세줄이 요약이다

 

aop?

 : 모든 로직 앞 뒤에 붙어서 모든 로직에 실행/ 로직 실행에 있어 걸리는 시간을 알 수 있다/관점 지향 프로그램

 

다 모르겠다싶으면 아래 한줄만이라도 알아가자

트랜잭션 =AOP

반응형

댓글