이 두코드의 차이가 뭘까
override fun findAndLockWarehouseByReservationTime(
warehouseName: String,
reservationTime: ReservationTime,
): WareHouse {
val wareHouseJpaEntity = wareHouseRepository.findByName(warehouseName)
reservationRepository
.findAndLockWarehouseByReservationTime(wareHouseJpaEntity.id, reservationTime)
return wareHouseJpaEntity.toDomain()
}
override fun findAndLockWarehouseByReservationTime(
warehouseName: String,
reservationTime: ReservationTime,
): WareHouse {
reservationRepository
.findAndLockWarehouseByReservationTime(warehouseName, reservationTime)
return wareHouseRepository.findByName(warehouseName).toDomain()
}
답은 바로
첫번째꺼는 락을 걸기 전 조회를 하고,락을 걸고,락을 걸기전에 조회한걸를 리턴했고
두번째꺼는 락을 걸고나서 락이 끝나고 조회를해서 리턴했다는거다
당연히 저러면 락걸리기 전 데이터가 리턴되고,동시삽입문제가 터진다
근데 문제는
override fun findAndLockWarehouseByReservationTime(
warehouseName: String,
reservationTime: ReservationTime,
): WareHouse {
val wareHouseJpaEntity = wareHouseRepository.findByName(warehouseName)
reservationRepository
.findAndLockWarehouseByReservationTime(wareHouseJpaEntity.id, reservationTime)
return wareHouseRepository.findByName(warehouseName).toDomain() //새로 조회
}
이렇게 새로 조회를 해도 안된다는거다
그래서 처음엔 영속성 컨텍스트에 남아있어서 그런가보다 하고
override fun findAndLockWarehouseByReservationTime(
warehouseName: String,
reservationTime: ReservationTime,
): WareHouse {
val wareHouseJpaEntity = wareHouseRepository.findByName(warehouseName)
reservationRepository
.findAndLockWarehouseByReservationTime(wareHouseJpaEntity.id, reservationTime)
entityManager.flush()
entityManager.clear()
return wareHouseRepository.findByName(warehouseName).toDomain()
}
이렇게 영속성 컨텍스트를 날려줘도 이전값(락이 걸리기 전 값,즉 wareHouseJpaEntity와 같은값)을 리턴했다
결국 이문제의 핵심은 mysql의 동작방식인데(예전실험링크)
mysql의 innoDB를 사용할때의 기본 격리수준인 Repeatable Read는,이름 그대로 반복가능한 읽기로,같은 트랜잭션 내에서는 같은 쿼리에 대해 항상 같은값을 리턴해준다(non repeatable read의 일종인 팬텀리드를 방지해준다,이건 이노디비의 특이점)
그런데 비관적 락인 select ~for update에서는 mysql도 팬텀리드방지를 포기하고,다른 트랜잭션이 중간에 커밋하면 그거에 따라 바뀌는 값을 던져주는데(일단 내가 뭘 할려고 락을걸었는데,그 락을 걸었을때의 상황을 아는게 더중요하니까),지금 나는 for update로 리턴을 하는게 아닌,일반 조회메서드로 2번 조회한다음에 리턴을 했기때문에 생기는 문제였다
결론
mysql jpa에서는,비관적락을 건 쿼리로 바로 리턴을 하던가,아니면 락이 풀리고 나서 조회를 해서 던져야한다
그래도 예전에 한번 당했던거라서 금방 찾았다
역시 모르면 맞아야지
'jpa' 카테고리의 다른 글
n+1문제 종류별 해결법 (0) | 2024.10.02 |
---|---|
jpa의 @Batchsize 동작방식 (0) | 2023.04.14 |