IT/디버깅

oneToMany가 연속적일 때 failed to lazily initialize a collection of role 에러 해결

happy_life 2024. 1. 28. 20:58

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" 에러가 발생하였습니다.

 

controller계층에서의 Converter class

 

 

 

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);

    }