조급하면 모래성이 될뿐

N + 1 직접 만났다 본문

TroubleShooting/데브코스

N + 1 직접 만났다

Pawer0223 2022. 7. 1. 02:54

Entity 관계


  • 1개의 스케줄에는 예매된 N개의 예매 좌석 정보가 존재한다.
    • ex) A영화관 B영화의 C 상영관의 13:00에 예매된 좌석은 0, 1, 2이다.
  • 예매 좌석 정보와 좌석 정보(seat)와 1:N 관계를 맺고 있다.
    • ReservedSeat(1)에서 Seat(N) 정보를 가져온다.

 

 

의도한 것


  • 스케줄 id로 검색해서 예약 좌석 정보를 가져온다 (ReservedSeat)
  • 좌석정보를 내려주기 위해 ReservedSeat에 포함된 Seat객체를 dto객체로 convert 한다.

 

AS-IS


  • ReservedSeatService.java
public List<ResponseFindSeat> findByScheduleId(Long scheduleId) {
    return reservedSeatRepository.searchByIdStartsWith(makeFindByScheduleParam(scheduleId)).stream()
        .map((reservedSeat -> seatConverter.convertFromSeatToResponseFindSeat(reservedSeat.getSeat())))
        .collect(toList());
}
  • ReservedSeatRepository
    • 그냥 named 쿼리 수행했다.
public interface ReservedSeatRepository extends JpaRepository<ReservedSeat, String> {
	List<ReservedSeat> searchByIdStartsWith(String scheduleId);
}

 

TO-BE


  • fetch join으로 한 번에  seat도 함께 가져오도록 변경했다.
public interface ReservedSeatRepository extends JpaRepository<ReservedSeat, String> {
	@Query("SELECT rs FROM ReservedSeat rs join fetch rs.seat WHERE rs.id LIKE :scheduleId%")
	List<ReservedSeat> searchByIdStartsWith(String scheduleId);
}

 

왜 못 잡았는가?


  • 테스트 코드를 작성하면서 영속성 콘텍스트에 데이터가 남아 있었다.
	@Test
	@DisplayName("스케줄 id로 좌석정보를 조회할 수 있다.")
	void testFindByScheduleId() {
		Ticket ticket = saveTicket();
		int seatCount = 3;

		IntStream.range(0, seatCount).forEach((seq) -> {
			Seat seat = saveSeat(ticket.getSchedule().getTheaterRoom(), 0, seq);
			ReservedSeat reservedSeat = new ReservedSeat(ticket, seat);
			reservedSeatRepository.save(reservedSeat);
		});


		List<ResponseFindSeat> seatList = reservedSeatService.findByScheduleId(ticket.getSchedule().getId());
		assertThat(seatList).hasSize(seatCount);
	}

 

  • 위 코드를 돌렸을 때 문제가 없었다.

 

  • 하지만 중간에 service를 호출하기 전에 영속성 콘텍스트를 비워주니.. select가 날아갔다.
	@Test
	@DisplayName("스케줄 id로 좌석정보를 조회할 수 있다.")
	void testFindByScheduleId() {
		Ticket ticket = saveTicket();
		int seatCount = 3;

		IntStream.range(0, seatCount).forEach((seq) -> {
			Seat seat = saveSeat(ticket.getSchedule().getTheaterRoom(), 0, seq);
			ReservedSeat reservedSeat = new ReservedSeat(ticket, seat);
			reservedSeatRepository.save(reservedSeat);
		});

		em.flush();
		em.clear();

		List<ResponseFindSeat> seatList = reservedSeatService.findByScheduleId(ticket.getSchedule().getId());
		assertThat(seatList).hasSize(seatCount);
	}

  • service에서 find 하고,  seat에 접근할 때마다 select가 날아갔다..

 

어떻게 잡았는가?


  • 코드 리뷰하면서 팀원분께서 짚어주셨다.. 👍

 

앞으로는?


  • 1차적으로 repository layer에서 검증을 더 꼼꼼히 잡아야겠다.
  • service에서 직접 데이터를 저장하는 경우 영속성 콘텍스트를 반드시 비워두고 돌려야겠다.
반응형