ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 @Transactional과 잠금
    Spring/Spring 2021. 8. 11. 11:27

    Spring Framework에서 @Transactional 어노테이션과 잠금의 차이를 알아보자. @Transactional 어노테이션을 사용하는 것만으로 메서드에 잠금이 걸리는 것은 아니다. @Transactional은 트랜잭션의 경계를 정의하고 트랜잭션 관리의 일관성을 유지하는 데 사용된다. 하지만 이는 동시성 제어나 잠금을 직접적으로 처리하는 것은 아니다.

    트랜잭션 관리와 잠금은 다르다.

     

     

    트랜잭션 관리:

    • @Transactional 어노테이션은 데이터베이스 트랜잭션을 시작하고, 커밋하거나 롤백하는 역할을 한다.
    • 트랜잭션 내에서 수행되는 모든 작업은 원자적으로 처리되며, 트랜잭션이 완료되기 전까지 다른 트랜잭션에서 해당 데이터에 접근할 수 없도록 보장된다.

    예를 들어, 사용자 계정 정보 업데이트와 같은 시나리오가 있다.

     

     

    사용자 계정 정보 업데이트 예시

    @Service
    public class UserService {
    
        @Autowired
        private UserRepository userRepository;
    
        @Transactional
        public void updateUserAccount(Long userId, String newEmail, String newPhoneNumber) {
            User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException());
            user.setEmail(newEmail);
            user.setPhoneNumber(newPhoneNumber);
        }
    }

    사용자가 자신의 계정 정보를 업데이트하는 경우, 일반적으로 다른 사용자가 동시에 같은 계정 정보를 수정하는 일이 없으므로 트랜잭션 관리만으로도 충분하다. 위 코드에서 @Transactional 어노테이션을 사용해 트랜잭션을 관리한다. 이 메서드는 특정 사용자의 계정 정보를 업데이트하는데, 일반적으로 하나의 계정은 하나의 사용자만 접근하기 때문에 동시성 문제는 발생하지 않는다. 따라서 별다른 잠금 메커니즘은 불필요하며, 트랜잭션 관리만으로도 충분한 일관성을 유지할 수 있다.

     

     

     

    잠금:

    • 잠금은 데이터의 동시 접근을 제어하기 위한 메커니즘이다.
    • JPA는 잠금을 관리하기 위해 @Lock 어노테이션이나 EntityManager의 lock 메서드를 사용할 수 있다.
    • 잠금 모드는 Pessimistic Locking과 Optimistic Locking으로 나뉘며, 각기 다른 방식으로 동시성을 제어한다.

     

    @Transactional 어노테이션은 메서드나 클래스에 적용하여 데이터베이스 트랜잭션의 경계를 정의한다. 이 어노테이션을 사용하면 트랜잭션 시작, 커밋, 롤백을 자동으로 처리할 수 있다. 하지만 이 어노테이션만으로는 동시성 문제를 해결하지 못한다.

    예를 들어, 좌석 예약 시스템을 봐보자.

    @Service
    public class ReservationService {
    
        @Transactional
        public void reserveSeat(Long seatId) {
            Seat seat = seatRepository.findById(seatId).orElseThrow(() -> new SeatNotFoundException());
    
            if (seat.isReserved()) {
                throw new SeatAlreadyReservedException("Seat already reserved");
            }
    
            seat.setReserved(true);
        }
    }

     

    위 코드에서는 @Transactional을 사용해 트랜잭션을 관리한다. 하지만 여러 사용자가 동시에 좌석을 예약하려고 하면 동시성 문제가 발생할 수 있다. 예를 들어, 두 사용자가 동시에 같은 좌석을 예약하려고 할 때, 두 트랜잭션이 동시에 진행되어 두 예약이 모두 성공할 수 있다.

     

    @Service
    public class ReservationService {
    
        @Transactional
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        public void reserveSeat(Long seatId) {
            Seat seat = seatRepository.findById(seatId).orElseThrow(() -> new SeatNotFoundException());
    
            if (seat.isReserved()) {
                throw new SeatAlreadyReservedException("Seat already reserved");
            }
    
            seat.setReserved(true);
        }
    }

     

    동시성 문제를 해결하기 위해 잠금을 사용한다. JPA에서는 @Lock 어노테이션이나 EntityManager의 lock 메서드를 통해 잠금을 설정할 수 있다. 잠금 모드는 비관적 잠금(Pessimistic Locking)과 낙관적 잠금(Optimistic Locking)으로 나뉘며 비관적 잠금으로 작성해보았다. @Transactional을 사용하더라도 별도의 잠금 설정 없이도 트랜잭션 내에서 데이터 일관성을 유지할 수 있지만, 동시성 문제를 해결하기 위해서는 추가적인 잠금 전략이 필요할 수 있다.

     

     

     

    스프링에서 @Transactional 어노테이션은 트랜잭션 경계를 정의하고 관리하는 데 유용하다. 하지만 동시성 문제를 해결하기 위해서는 추가적인 잠금 전략이 필요하다.  데이터 일관성만 보장해야 하는 경우, 동시성을 고려하지 않아도 괜찮다고 여기어 지는 경우에는 @Transactional 어노테이션을 사용해 트랜잭션 경계를 정의하고 관리하면 된다. 잠금 메커니즘은 불필요하며, 트랜잭션 관리만으로도 충분한 일관성을 유지할 수 있다.

Designed by Tistory.