예매 상태의 티켓을 취소 상태로 변환하는 API({{dev_url}}/api/tickets/{{티켓id}})에서 티켓 정보와 함께 쿼리가 하나 더 발생하는 현상이 있었습니다. 쿼리 발생 문제는 티켓을 취소 상태로 업데이트하는 과정에서 발생했습니다.
문제가 발생한 부분은 TicketController와 TicketServiceImpl입니다.
// TicketController.class
@DeleteMapping("/{ticketId}")
public ApiResponse<Void> cancelTicket(
@AuthenticationPrincipal Member member, @PathVariable(name = "ticketId") long ticketId) {
ticketService.cancelTicket(member.getEmail(), ticketId);
return ApiResponse.ok();
}
// TicketServiceImpl.class
@Override
@Transactional(transactionManager = "jpaTransactionManager")
public void cancelTicket(String email, Long ticketId) {
Ticket ticket = ticketRepository.findByMember(ticketId, email)
.orElseThrow(() -> new CustomException(TICKET_NOT_FOUND_ERROR));
ticket.isCancellable();
updateTicketStatus(ticket, TicketStatus.CANCELED);
}
TicketServiceImpl에서 ticketRepository.findByMember(ticketId, email)를 호출하여 티켓 정보를 조회한 후, 티켓 상태를 업데이트합니다.
//TicketEntity
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "musical_id")
private Musical musical;
@Column
@Enumerated(value = EnumType.STRING)
private TicketStatus ticketStatus;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "seat_id")
private Seat seat;
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@Column
private LocalDateTime validUntil;
@Column
private LocalDateTime cancelUntil;
@Column
private String deviceId;
...
Hibernate 쿼리를 확인한 결과, 티켓 정보와 함께 Musical 테이블에 대한 쿼리도 실행되고 있었습니다.
-- Query 1: Fetching ticket information
SELECT
ticket0_.id AS id1_7_,
ticket0_.cancel_until AS cancel_u2_7_,
ticket0_.created_at AS created_3_7_,
ticket0_.device_id AS device_i4_7_,
ticket0_.member_id AS member_i7_7_,
ticket0_.musical_id AS musical_8_7_,
ticket0_.seat_id AS seat_id9_7_,
ticket0_.ticket_status AS ticket_s5_7_,
ticket0_.valid_until AS valid_un6_7_
FROM
ticket ticket0_
CROSS JOIN
member member1_
WHERE
ticket0_.member_id = member1_.id
AND ticket0_.id = ?
AND member1_.email = ?
-- Query 2: Fetching musical information
SELECT
musical0_.id AS id1_3_0_,
musical0_.detail_image_url AS detail_i2_3_0_,
musical0_.notice_image_url AS notice_i3_3_0_,
musical0_.place AS place4_3_0_,
musical0_.place_detail AS place_de5_3_0_,
musical0_.place_image_url AS place_im6_3_0_,
musical0_.poster_image_url AS poster_i7_3_0_,
musical0_.ranking AS ranking8_3_0_,
musical0_.running_time AS running_9_3_0_,
musical0_.ticketing_end_date AS ticketi10_3_0_,
musical0_.ticketing_start_date AS ticketi11_3_0_,
musical0_.title AS title12_3_0_
FROM
musical musical0_
WHERE
musical0_.id = ?
처음에는 N+1 문제로 의심했으나, 이 문제는 아니었습니다.
원인은 Ticket 엔터티의 musical 필드가 기본적으로 Eager로 설정되어 있었기 때문입니다.
@ManyToOne
@JoinColumn(name = "musical_id")
private Musical musical;
위 코드에서 FetchType.LAZY를 명시하지 않았기 때문에 Musical 객체가 함께 조회된 것입니다.
FetchType.LAZY로 변경하여 해결했습니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "musical_id")
private Musical musical;