20241113 TIL : Service에서 @Transactional의 적용

MCS·2024년 11월 13일

TIL

목록 보기
3/45

오늘 진행한 학습

  • Service에서 @Transactional의 적용
    • 트랜잭션이란 무엇인가?
    • @Transactional을 CRUD 중 어디에 적용해야 할까?
    • 프로젝트 Service 코드에 적용한다면?

Service에서 @Transactinal의 적용

트랜잭션이란 무엇인가?

트랜잭션은 DB의 상태를 변화시키기 위해 수행하는 작업의 단위

DB에서는 데이터의 읽기, 쓰기, 수정 작업이 동시에 수행된다. 이 때, 서로의 작업 결과가 데이터에 영향을 미쳐 의도하지 않은 데이터가 생길 수 있다.

Ex) 두 사용자가 동시에 동일한 계좌에서 돈을 이체하려는 상황을 가정한다.

  • 사용자 1과 사용자 2가 각각 100만원을 계좌 A에서 출금하려고 한다.
  • 계좌 A의 잔액은 200만원이다.

이 두 작업이 동시에 처리될 때, 다음과 같은 문제가 발생할 수 있다.

  1. 사용자 1과 2는 계좌 A에 200만원이 있는 것을 확인하고 출금을 시도한다.
  2. 사용자 1은 계좌 A에서 100만원 출금 요청을 보낸다.
  3. 사용자 2도 계좌 A에서 100만원 출금 요청을 보낸다.
  4. 사용자 1과 2의 이체가 동시에 이루어지면서, 계좌에서 100만원씩 빠져나가면 총 200만원이 출금되어 잔액이 없어야 하지만, 실제 계좌에는 100만원이 남게 된다.

위 예시에서, 두 작업 모두 잔액에서 100만원을 차감하는 수정이 이루어 질 것이다. 하지만 두 작업 모두 수행 시점에서 잔액이 200만원이었기 때문에, 결국 잔액이 100만원이 되는 데이터 불일치가 발생한다.

이러한 문제를 해결하기 위해 DB에서는 트랜잭션을 사용한다. 트랜잭션은 ACID라는 속성을 가지고 있다.

  • 원자성(Atomicity)
    • 트랜잭션 내의 작업이 모두 성공하면 Commit되어 DB에 반영되고, 하나라도 실패하면 Rollback되어 이전 상태로 돌아간다.
  • 일관성(Consistency)
    • 트랜잭션의 작업 처리 결과가 항상 일관된 데이터베이스 상태를 보존하며, 데이터베이스의 모든 무결성 제약조건을 만족한다.
  • 격리성(Isolation)
    • 여러 개의 트랜잭션이 동시에 실행될 때, 트랜잭션은 서로를 참조하거나 영향을 미칠 수 없다.
    • 동시에 실행되는 트랜잭션들이 서로 격리되어 마치 순차적으로 실행되는 것처럼 보인다.
  • 지속성(Durability)
    • 트랜잭션이 commit되면 그 데이터는 DB에 영구적으로 반영된다.

위의 예시에서는 격리성(Isolation)이 문제를 해결하는 데 도움을 준다.

트랜잭션은 데이터의 무결성과 일관성을 보장하고, 동시성 문제를 해결하는 데 도움을 준다.

@Transactional을 CRUD 중 어디에 적용해야 할까?

Spring에서는 @Transactional 어노테이션을 통해 메서드 내의 DB 작업들이 하나의 트랜잭션으로 묶이도록 만들어 준다.
Repository의 메서드들은 @Transactional이 자체 적용되어 있다. 그렇다면 왜 @Transactional 어노테이션을 적용해야 할까? 현재 프로젝트에서는 영속성 컨텍스트를 유지시켜 변경 감지(Dirty Checking)를 사용하기 위해 사용했다.

public void updateUser(Long id, String newName) {
        User user = userRepository.findById(id).get();
        // findById는 내부적으로 트랜잭션이 있지만
        
        user.setName(newName);
        // 이 시점에서는 트랜잭션이 이미 종료되어
        // 변경 감지가 동작하지 않음
    }

@Transactional을 지정하지 않으면 변경 감지가 일어나지 않는다. @Transactional이 지정되어 있어야 메서드가 종료될 때 까지 영속성 컨텍스트가 유지되기 때문이다.
그렇다면 update 메서드에만 @Transactional을 적용하면 될까? 아래 내용은 이와 관련한 피드백이다.

  • update, delete 필수
  • create에도 rollback할 상황을 대비해 필요
  • Read에는 지연 로딩시에 필요할 수 있다.
  • sevice 클래스전체에 @Transactional(readOnly=true)옵션을 두고, create, update, delete 메서드에만 @Transactional을 지정

@Transactional을 지정하는 범위는 우아한형제들 개발자와 @Transactional 컨벤션을 어떻게할지 의논해서 결정한 내용이라고 한다.

@Transactional(readOnly = true)
//@어노테이션 ...
public class 000Service {
	
	//필드 ...
	
	public 읽기() {
	
	}
	
	@Transactional
	public 쓰기() {
	
	}

	@Transactional	
	public 업데이트() {
	
	}
	
	@Transactional	
	public 삭제() {
	
	}
}

프로젝트 Service 코드에 적용한다면?

현재는 update가 발생하는 메서드에서 변경 감지(Dirty Checking)를 사용하기 위해 update와 delete 메서드에 @Transactional 어노테이션을 사용하고 있다. (delete 메서드는 soft delete를 위해 DB의 delete_date와 delete_by 필드를 업데이트 하므로 업데이트라고 판단했다.)

@Transactional
    public UsernameResponseDto updateUser(@Valid UpdateUserRequestDto requestDto, String username) {
        User user = userRepository.findById(username)
                .orElseThrow(() -> new NullPointerException(ExceptionMessage.USER_NOT_FOUND.getMessage()));

        checkDeletedUser(user);

        user.update(requestDto, passwordEncoder);

        userRepository.save(user);

        return new UsernameResponseDto(user.getUsername());
    }

피드백 내용을 바탕으로, 클래스에 @Transactional(readOnly=true)을 지정하고, create메서드에도 @Transactional을 지정하는 방향으로 리팩토링을 진행할 예정이다.

profile
백엔드를 잘 하고 싶은 사람

0개의 댓글