티스토리 뷰

1. @Transactional 이란?

@Transactional 어노테이션은 트랜잭션에 대한 전파, 격리, 시간 초과, 읽기 전용 및 롤백 조건을 설정할 수 있고,

트랜잭션 관리자를 지정할 수도 있다. 해당 어노테이션은 클래스 또는 메서드에 선언이 가능하고 클래스에 선언시

모든 메소드에 적용이 되고 특정 메소드에 선언시 선언된 메소드에만 적용이 된다. 

개발 로직에 맞춰서 알맞게 사용을 하여야 한다.

 

2. 적용 우선 순위

1. 클래스의 메소드

2. 클래스

3. 인터페이스의 메소드

4. 인터페이스

 

3. @Transactional의 예제

@Transactional 선언

@Service
@RequiredArgsConstructor
public class MemberService{
	private MemberMapper memberMapper;
    
	@Transactional
	public void createMember(MemberDto memberDto){
		memberMapper.createMember(memberDto)
	}
}

@Transactional 사용법

@Service
@RequiredArgsConstructor
public class AccountService{
	private AccountMapper accountMapper;
    
    @Transactional
    public void save(AccountDto accountDto){
        Long senderId = accountDto.getSenderId();
        Long receiverId = accountDto.getReceiverId();
        Integer amount = accountDto.getAmount();
        // 1.송금
        send(senderId, amount);
        // 2.수취
        receive(receiverId, amount);
        // 3.이체
        accountMapper.save(transfer);
    }
    
    private void send(Long senderId, Integer amount){
        userService.sendAmount(senderId, amount);
    }

    private void receive(Long receiverId, Integer amount){
        userService.receiveAmount(receiverId, amount);
    }
}

위 코드의 이체 서비스의 예로 로직상 송금 -> 수취 -> 이체 순으로 처리를 하도록 되어 있다.

여기서 송금,수취,이체는 하나의 트랜잭션으로 묶여 처리되며, 3가지의 로직을 처리중 어느 하나의 로직에서라도

Exception이 발생시 모두 롤백처리가 된다.

읽기 전용(Read Only)

@Service
@RequiredArgsConstructor
public class MemberService{
	private MemberMapper memberMapper;
    
    @Transactional(readOnly = true)
	public MemberDto getMember(MemberDto memberDto){
    	return memberMapper.getMember(memberDto);
    }
}

읽기 전용인 경우 위 코드와 같이 readOnly값을 true로 선언하면 런타임시 최적화가 가능하다.

타임아웃(Time Out)

@Service
@RequiredArgsConstructor
public class MemberService{
	private MemberMapper memberMapper;
    
    @Transactional(readOnly = true, timeout = 10)
	public MemberDto getMember(MemberDto memberDto){
    	return memberMapper.getMember(memberDto);
    }
}

트랜잭션의 시간 제한을 위 코드와 같이 timeout값으로 설정 할 수 있으며, 단위는 초단위이다.

4. 트랜잭션 격리 수준 [Transaction Isolation Level]

1. 트랜잭션 격리 수준이란?

동시에 여러 트랜잭션이 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있도록 

허용할지 말지를 결정한다.

 

2. 트랜잭션 격리 수준의 종류

  • READ UNCOMMITTED
  • READ COMMITED
  • REPEATABLE READ
  • SERIALIZABLE

READ UNCOMMITTED

다른 트랜잭션에서 COMMIT되지 않은 데이터들을 읽어올 수 있는 Level이며, 만약 트랜잭션이 COMMIT 되지 않고 ROLLBACK 된다면 데이터베이스에 존재하지 않는 데이터를 읽어오게 된다. 이렇게 읽어온 신뢰할 수 없는 데이터를 'Dirty Read' 라고 하고 READ UNCOMMITTED Level에서는 아래와 같은 3가지 종류의 현상이 발생할 수 있다.

- Dirty Read: 아직 COMMIT되지 않은 신뢰할 수 없는 데이터를 읽어오는 현상

- Non-Repeatable Read: 한 트랜잭션에서 동일한 SELECT 쿼리의 결과가 다르게 나오는 현상

- Phantom Read: 이전의 SELECT 쿼리의 결과에 없던 Row가 생기는 현상

READ COMMITED

다른 트랜잭션에서 COMMIT된 데이터만 읽어올 수 있는 Level이며, READ UNCOMMITTED의 경우와 다르게 COMMIT하지 않은 데이터는 SELECT 쿼리로 볼 수 없다. 따라서 READ COMMITTED Level에서는 Dirty Read 현상이 발생하지 않지만 아래와 같이 나머지 2개의 현상은 발생할 수 있습니다.

- Dirty Read: 아직 COMMIT되지 않은 신뢰할 수 없는 데이터를 읽어오는 현상

- Non-Repeatable Read: 한 트랜잭션에서 동일한 SELECT 쿼리의 결과가 다르게 나오는 현상

- Phantom Read: 이전의 SELECT 쿼리의 결과에 없던 Row가 생기는 현상

REPEATABLE READ

이 Level에서는 트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회하더라도 항상 같은 데이터만 조회됩니다. MySQL에서는 REPEATABLE READ Level을 기본 값으로 설정했습니다(트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽는 원리).

REPEATABLE READ Level에서는 Dirty Read와 Non-Repeatable Read 현상이 발생하지 않습니다. 하지만 아래와 같이 Phantom Read 현상은 발생할 수 있습니다.

- Dirty Read: 아직 COMMIT되지 않은 신뢰할 수 없는 데이터를 읽어오는 현상

- Non-Repeatable Read: 한 트랜잭션에서 동일한 SELECT 쿼리의 결과가 다르게 나오는 현상

- Phantom Read: 이전의 SELECT 쿼리의 결과에 없던 Row가 생기는 현상

SERIALIZABLE

가장 엄격한 격리 수준이고 REPEATBLE READ가 동시성과 안정성의 균형을 가장 잘 갖춘 Isolation Level이었다고 한다면, SERIALIZABLE은 동시성을 상당 부분 포기하고 안정성에 큰 비중을 둔 Level이다. 이 Level에서는 한 트랜잭션안에서 단순 SELECT 쿼리를 사용하더라도 읽어온 모든 Row들에 Share Lock 또는 Read Lock을 적용시시고, 해당 Row의 Lock이 풀리기 전까지 INSERT, UPDATE, DELETE가 불가하여 Dirty Read, Non-Repeatable Read, Phantom Read 문제점을 모두 커버할 수 있다.

- Dirty Read: 아직 COMMIT되지 않은 신뢰할 수 없는 데이터를 읽어오는 현상

- Non-Repeatable Read: 한 트랜잭션에서 동일한 SELECT 쿼리의 결과가 다르게 나오는 현상

- Phantom Read: 이전의 SELECT 쿼리의 결과에 없던 Row가 생기는 현상

트랜잭션 격리 예제

@Service
@RequiredArgsConstructor
public class MemberService{
	private MemberMapper memberMapper;
    
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
	public void createMember(MemberDto memberDto){
    	return memberMapper.createMember(memberDto);
    }
    
    @Transactional(isolation = Isolation.READ_COMMITTED)
	public void updateMember(MemberDto memberDto){
    	return memberMapper.createMember(memberDto);
    }
    
    @Transactional(isolation = Isolation.REPEATABLE_READ)
	public void modifyMember(MemberDto memberDto){
    	return memberMapper.createMember(memberDto);
    }
    
    @Transactional(isolation = Isolation.SERIALIZABLE)
	public void deleteMember(MemberDto memberDto){
    	return memberMapper.createMember(memberDto);
    }
}

5. 트랜잭션 전파 행위(Transaction Propagation Behavior)

트랜잭션 전파란 현재 트랜잭션에서 다른 트랜잭션으로 이동할 때를 이야기 한다.
​스프링(Spring)에서 일반적으로 트랜잭션 영역 내에서 실행되는 모든 코드는 그 트랜잭션 내에서 실행되고, 만약 트랜잭션 컨텍스트가 이미 존재하는 상황에서 트랜잭션인 메소드가 실행된다면 
그 동작을 지정하는 몇가지 옵션들이 존재한다.

  • MANDATORY
  • NESTED
  • NEVER
  • NOT_SUPPORTED
  • REQUIRED
  • REQUIRES_NEW
  • SUPPORTS

MANDATORY

트랜잭션이 존재할 경우 해당 트랜잭션을 이용하며, 존재하지 않을 경우 예외를 발생시킵니다.

@Transactional(propagation = Propagation.MANDATORY)
public void transactionExample(String user) { 
    // ... 
}

NESTED

트랜잭션이 존재할 경우 중첩된 트랜잭션(nested transaction)을 생성하여 처리를 수행하고, 존재하지 않을 경우는 REQUIRED와 동일하게 동작합니다.

@Transactional(propagation = Propagation.NESTED)
public void transactionExample(String user) { 
    // ... 
}

NEVER

트랜잭션이 존재할 경우 예외를 발생시키고, 트랜잭션이 없는 상태로 처리를 수행합니다.

@Transactional(propagation = Propagation.NEVER)
public void transactionExample(String user) { 
    // ... 
}

NOT_SUPPORTED

트랜잭션이 존재할 경우 잠시 보류시키고, 트랜잭션이 없는 상태로 처리를 수행합니다.

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void transactionExample(String user) { 
    // ... 
}

REQUIRED

트랜잭션이 존재하는 경우 해당 트랜잭션을 그대로 사용하고, 트랜잭션이 없는 경우 트랜잭션을 생성합니다.

@Transactional(propagation = Propagation.REQUIRED)
public void transactionExample(String user) { 
    // ... 
}

REQUIRES_NEW

트랜잭션이 존재하는 경우 해당 트랜잭션을 잠시 보류시키고, 신규 트랜잭션을 생성합니다.

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transactionExample(String user) { 
    // ... 
}

SUPPORTS

트랜잭션이 존재할 경우 해당 트랜잭션을 사용하고, 존재하지 않을 경우는 트랜잭션을 사용하지 않습니다.

@Transactional(propagation = Propagation.SUPPORTS)
public void transactionExample(String user) { 
    // ... 
}

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함