Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 형상관리
- aop
- multimodule testcontainers
- jpa
- OptimisticLock
- spring aop
- interface
- DI
- RefreshToken
- ObjectOptimisticLockingFailureException 처리
- redissonlock aop
- ObjectOptimisticLockingFailureException
- netty
- springsecurity
- spring DI
- S3
- @transactional
- 알고리즘
- 낙관적 락 재시도
- TestContainers
- 소수찾기 java
- Spring Cloud Gateway
- 우아한 테크러닝
- 백준
- kotest testcontainers
- 낙관적 락 롤백
- AccessToken
- java
- Invalid property 'principal.username' of bean class
- 멀티모듈 테스트컨테이너
Archives
- Today
- Total
조급하면 모래성이 될뿐
Service에서 DataIntegrityViolationException을 Catch 못함 본문
TroubleShooting/데브코스
Service에서 DataIntegrityViolationException을 Catch 못함
Pawer0223 2022. 7. 14. 02:40문제 상황
- 동시에 같은 좌석이 예약이 되는 경우 적절한 예외를 발생시키고 싶었다.
- 현재 프로젝트에서 동시에 같은 좌석이 예약되는 경우 DataIntegrityViolationException이 발생한다
- id의 조합으로 String타입 id를 생성한다.
- 따라서 동일한 id조합일 때 insert 할 때 예외가 발생한다.
- 해당 예외가 발생했을 때 예외 포인트를 정확히 기록하고자 try-catch로 감싸서 IllegalArgumentException을 발생시켰다.
- 하지만 예외가 잡히지 않았다...
테스트 코드
@Test
@DisplayName("예약 실패 - IllegalException")
void testReservationIllegalException() {
// given
User user = saveUser();
Schedule schedule = saveSchedule();
int totalSeatCount = 1;
TheaterRoom theaterRoom = saveSeatMulti(totalSeatCount);
List<Seat> seats = seatRepository.findByTheaterRoomId(theaterRoom.getId());
List<Long> seatIdList = seats.stream().map(Seat::getId).toList();
Integer userMoney = totalSeatCount * PAY_AMOUNT * 3;
Account account = new Account(user, userMoney);
accountRepository.save(account);
RequestReservation request = new RequestReservation(user.getId(), schedule.getId(), seatIdList);
reservationService.reservation(request);
// when
assertThatThrownBy(() -> reservationService.reservation(request))
// then
.isInstanceOf(IllegalArgumentException.class);
}
AS-IS
@Service
@Transactional(readOnly = true)
public class ReservationService {
private static final int PAY_AMOUNT = 10000;
@Transactional
public Long reservation(RequestReservation requestReservation) {
User findUser = userService.findById(requestReservation.userId());
// 결제
List<Long> seats = requestReservation.selectedSeatIds();
int payAmount = PAY_AMOUNT * seats.size();
payService.pay(findUser, payAmount);
//티켓 저장
Schedule findSchedule = scheduleService.findById(requestReservation.scheduleId());
Ticket savedTicket = ticketService.createTicket(findUser, findSchedule, payAmount);
// 좌석 예약 처리
List<Seat> findSeats = seatService.findSeatsByIdIn(seats);
List<ReservedSeat> selectedSeats = findSeats.stream()
.map(seat -> new ReservedSeat(savedTicket, seat))
.collect(Collectors.toList());
try {
reservedSeatService.saveAll(selectedSeats);
} catch (DataIntegrityViolationException e) {
throw new IllegalArgumentException(ALREADY_RESERVED_SEAT_EXP_MSG.getMessage());
}
return savedTicket.getId();
}
...
}
- try-catch에 디버깅을 찍고 잡아도 걸리질 않았다.
- 모든 예외를 잡도록 Exception으로 Catch를 추가해도 안 잡혔다..
뭐가 문제?
- 디버깅에서 Service에서 예외가 잡히지 않았다.
- 즉, Service에서 예외가 발생하지 않는다.
이유는 DataIntegrityViolationException 공식 문서를 보면 어느 정도 이해가 된다.
데이터를 삽입하거나 업데이트하려는 시도가 무결성 제약 조건을 위반할 때 throw 되는 예외입니다. 이것은 순전히 관계형 개념이 아닙니다. 고유 기본 키는 대부분의 데이터베이스 유형에 필요합니다.
- Service함수 자체에서는 해당 Entity를 영속화하고, flush가 수행되지 않는다.
- 따라서 트랜잭션이 끝나기 직전에 해당 예외가 발생하게 된다.
- 즉, 내가 잡은 try부분인 saveAll을 하는 시점에는 예외가 발생하지 않고 쿼리가 날아갈 때 발생한다.
해결방법
- try 하는 부분에서 쿼리가 날아가도록 하면 된다.
- JpaRepository에서 제공하는 saveAllAndFlush를 적용해주면 된다.
- 아니면.. Service를 사용하는 외부.. 즉, 컨트롤러 같은 데서 잡을 수도 있을 거다.
- 시도는 안 해봄..
TO-BE
- service에서 JpaRepository의 saveAll을 호출하던 것을 saveAllAndFlush로 변경해주면 끝이다.
- 트랜잭션이 끝나기 전에 쿼리를 수행함으로써 중복으로 저장되는 예약좌석을 감지할 수 있다.
@Service
@Transactional(readOnly = true)
public class ReservedSeatService {
...
@Transactional
public void saveAllAndFlush(List<ReservedSeat> selectedSeats) {
reservedSeatRepository.saveAllAndFlush(selectedSeats);
}
...
}
반응형
'TroubleShooting > 데브코스' 카테고리의 다른 글
Restdocs와 Swagger UI 연동해서 EC2에 배포하기 (0) | 2022.08.04 |
---|---|
CodeDeploy 적용기 (0) | 2022.07.26 |
S3 파일 업로드 (0) | 2022.07.11 |
Service에서 다른 Service를 의존하게 된다면 ? (0) | 2022.07.06 |
N + 1 직접 만났다 (0) | 2022.07.01 |