member에 ticket이 1:N으로 있고 ticket에 1:N으로 file이 있는 연관관계에서 LazyLoadingException이 발생하여서 문제를 해결하는 과정을 기록합니다. nested fetch를 하려고 하였으나, org.hibernate.loader.MultipleBagFetchException이 발생하여 쿼리를 분리하던 과중에서 생긴 문제입니다.
LazyLoading 문제상황
@Override
public Member findMemberWithTicketsAndMemberTeamsByMemberId(Long memberId) {
Member memberWithTickets = memberRepository.findMemberWithTicketsById(memberId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND, "member를 찾을 수 없습니다."));
List<Long> ticketIdList = memberWithTickets.getTicketList().stream()
.map(Ticket::getId)
.collect(Collectors.toList());
List<File> filesByTicketIds = fileRepository.findFilesByTicketIds(ticketIdList);
return memberWithTickets;
}
전략은 모두 LazyLoading이고 Service Layer 코드에서 member를 Return하면 Controller에서 Converter Class로 dto로 변환하는 과정에서 문제가발생하였습니다. 구체적으로 member를 받아오는 과정에서 file은 정상적으로 loading되어있으나, Controller 계층에서 MemberDto로 변환하는 코드에서 file이 0개인 경우 "failed to lazily initialize a collection of role: umc.tickettaka.domain.ticket.Ticket.fileList: could not initialize proxy - no Session" 에러가 발생하였습니다.
LazyLoading 문제상황
해결방법1. Controller에 @Transactional 적용 -> X Contoller는 사용자의 요청을 받아 처리하는 역할을 맡고, 비지니스 로직을 담고 있지 않아야 하므로 트랜잭션을 사용할 필요가 없는 경우가 많습니다. 또한 트랜잭션을 사용하면, DB를 연결한 채로 사용자의 요청을 처리하게 되는데 이는 효율성과 확장성에 부정적인 영향을 줄 수 있다고 합니다. 또한 @Controller 단위에서 lazyLoading 되었던 것을 다시 loading 하려고 N+1 문제가 발생하였습니다.
해결방법2. JPA에 대한 이해
받아오는 DB를 확인해보니 member에 ticket이 연관관계가 매핑되어있었고, file에 ticket이 매핑되어있었으나, ticket에는 file이 매핑이 안되는 것같았습니다. 쿼리가 findA with B, find B with C가 되어야 JPA에서 인식해서 연관관계를 객체에 꽂아줄텐데, find A with B, find C in (B's idList)인 형식으로 되어서 문제가 발생한 것같았습니다. 따라서 아래와 같은 코드로 수정해 문제를 해결하였습니다.
@Override
public List<Object> findMemberWithTicketsAndMemberTeamsByMemberId(Long memberId) {
Member memberWithTickets = memberRepository.findMemberWithTicketsById(memberId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND, "member를 찾을 수 없습니다."));
Member memberWithMemberTeams = memberRepository.findMemberWitMemberTeamById(memberId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND, "member를 찾을 수 없습니다."));
List<Ticket> tickets = ticketRepository.findAllWithFileByWorkerOrderByEndTime(memberWithMemberTeams.getId());
return Arrays.asList(memberWithMemberTeams, tickets);
}
'IT > 디버깅' 카테고리의 다른 글
한진정보통신 인턴 과제 - 특정 라벨에 대한 yolo8 모델 정확도 문제 해결 과정 정리 (0) | 2024.06.04 |
---|---|
spring security filter exception 처리 이슈 해결 (0) | 2024.02.15 |
jwt 사용 시 remember me 적용 안되는 이슈 (0) | 2024.01.25 |
@WithMockUser 사용시 요청에 사용되는 User와 MockUser가 다른 경우 발생하는 에러 401 (0) | 2023.11.11 |
"query specified join fetching, but the owner of the fetched association was not present in the select list" 에러 원인과 해결 (0) | 2023.10.28 |