다음과 같이 영화관 자리를 예매하는 예제가 있다고 할 때
여러명이 동시에 빈 자리를 확인하고 예매를 할 경우 동시에 예매가 되어버리는 문제가 있습니다.
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class MovieBookingService(private val seatRepository: SeatRepository) {
/**
* 1. 예매정보가 있는지 조회
* 2. 없다면 예매
*/
@Transactional
fun bookMovieSeat(hallNumber: String, seatNumber: String): Pair<Boolean, String> {
val seat = seatRepository.findByHallNumberAndSeatNumber(hallNumber, seatNumber)
if (seat != null) {
if (seat.isBooked) {
// 이미 예매된 좌석인 경우
return Pair(false, "")
} else {
// 좌석 예매 처리
seat.isBooked = true
seatRepository.save(seat)
val bookingNumber = BookingNumberGenerator.generateBookingNumber()
return Pair(true, bookingNumber)
}
} else {
// 해당 좌석이 존재하지 않는 경우, 새로운 좌석을 생성하여 예매 처리
val newSeat = Seat(hallNumber = hallNumber, seatNumber = seatNumber, isBooked = true)
seatRepository.save(newSeat)
val bookingNumber = BookingNumberGenerator.generateBookingNumber()
return Pair(true, bookingNumber)
}
}
}
동시에 여러 사용자가 같은 자리를 예매하려고 할 때, 한 사용자가 조회한 결과를 기반으로 다른 사용자가 예매하는 경우 중복 예매가 발생할 수 있습니다. 이러한 상황을 방지하기 위해 조회 로직에 비관적 락을 걸어서 동시에 여러 사용자가 같은 자리에 대한 예매를 시도할 경우, 하나의 사용자가 해당 자리에 대한 락을 획득하고 예매 작업을 수행하게 됩니다. 다른 사용자들은 해당 락이 해제될 때까지 대기하게 됩니다.
비관적 락은 데이터 일관성과 동시성 문제를 해결하는 데 도움이 되는 방법입니다. 예매 시스템에서는 중복 예매를 방지하기 위해 이러한 비관적 락을 사용하는 것이 적절합니다.
추가적으로, 비관적 락을 사용할 때 주의해야 할 점은 데드락에 대한 가능성입니다. 데드락은 두 개 이상의 트랜잭션이 서로가 소유한 락을 기다리는 상태로 빠지는 상황을 말합니다. 이를 방지하기 위해 락을 걸 순서를 잘 고려해야 합니다. 일반적으로 락을 걸 순서는 공통적인 규칙을 따르는 것이 좋습니다.
또한, 예매 시스템에서 락을 걸 때, 락의 범위를 가능한 작게 가져가는 것이 성능상 이점을 가져올 수 있습니다. 예를 들어, 특정 좌석에 대한 락을 걸지 않고 전체 상영관에 대한 락을 걸 경우, 동시에 여러 사용자가 다른 상영관의 좌석을 예매하는 것도 락의 영향을 받아 대기하게 될 수 있습니다. 이런 경우에는 필요한 범위만큼 락을 구체적으로 걸어 성능을 최적화할 수 있습니다.
Spring Data JPA에서 비관적 락을 사용하는 방법
@Repository
interface SeatRepository : JpaRepository<Seat, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT s FROM Seat s WHERE s.hallNumber = :hallNumber AND s.seatNumber = :seatNumber")
fun findByHallNumberAndSeatNumber(hallNumber: String, seatNumber: String): Seat?
//네이티브 쿼리에서는 for update라는 구문을 추가하면 된다.
//@Query(value = "SELECT * FROM seat s WHERE s.hall_number = :hallNumber AND s.seat_number = :seatNumber FOR UPDATE", nativeQuery = true)
//fun findByHallNumberAndSeatNumberForUpdate(hallNumber: String, seatNumber: String): Seat?
}
@Lock(LockModeType.PESSIMISTIC_WRITE) 을 적어주면 됩니다
이로써 A와 B가 동시에 조회 하는 경우 A 사용자의 트랜잭션이 끝날 때 까지 B는 대기하게 됩니다.
B는 A의 업데이트 된 정보를 조회하게 될 것입니다.
'Programing > Spring Boot' 카테고리의 다른 글
| 안드로이드 notification 뱃지 제거하기, 카운트 수정하기 (0) | 2023.11.25 |
|---|---|
| 오류) Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. (0) | 2023.08.28 |
| 코틀린 Querydsl Pageble Sort 동적 정렬 쉽게 사용하기 + 유틸화 (0) | 2023.07.13 |
| 스프링부트 스케줄러가 여러번 실행된다면? @SchedulerLock 사용법 (0) | 2023.07.06 |
| SpringBoot @RequestBody 데이터 선택적으로 받기 (0) | 2023.07.04 |