JPQL은 만능이 아니다!

2021. 12. 23. 19:05Spring/JPA

Spring Boot JPA JPQL 문제

JPQL은 만능이 아니다

  • 되게 오랜만에 글을 쓰네요! 입사를 하고 나서 바쁘고,, 돈을 받으니 하고 싶었던걸 하다보니 블로그 작성을 하지 않았는데 .. 껄껄 다시 작성하려고 합니당
    이번에 맞닥드린 문제는 JPQL를 사용하면서 데이터를 뽑아낼 때 처한 문제를 풀어내보려고 합니다. 일단 제목이 스포를 해버렸네요ㅎㅎ

조회 쿼리

  • Client에서 RequestParam으로 데이터를 받아서 해당 되는 데이터를 DTO로 핏하게 보내주는 작업을 하고 있었습니다.

        @Query
            (
                "SELECT new com.imaslab.fms.dtos.car.OnsiteServiceReceiverManageDTO(fu.id, fu.name, fu.phone, fc.name, rs.id, rs.name, COALESCE(orm.onsiteCancel, false ) , COALESCE(orm.onsiteWaiting, false ) , COALESCE(orm.onsiteConfirmed, false ) , COALESCE(orm.onsiteOperated, false )) " +
                    "FROM FMSUser fu " +
                    "LEFT JOIN OnsiteReceiverManage orm ON orm.fmsUser.id = fu.id " +
                    "LEFT JOIN FMSCompany fc ON fc.id = fu.companyId " +
                    "INNER JOIN fu.roles rs " +
                    "WHERE rs.name = "INSTALLER" " +  
                                    "AND coalesce(:userList, NULL) IS NULL OR (fu.id IN :userList) " +
                    "ORDER BY orm.updatedAt desc "
            )
        List<OnsiteServiceReceiverManageDTO> findReceiverManage(List<Long> userList);
    • 이런식으로 조회 쿼리를 작성했습니다. User와 Role 테이블은 다대다 관계를 맺고 있어서 JOINTABLE로 연관관계를 맺었습니다. 이미 작성되어있는 코드여서 다대일, 일대다로 풀어내기가... 힘들어서 그대로 사용했습니다.

    • 여기서 문제점은 WHERE 절에서 rs.name = INSTALLER 라는 조건문이 제가 원했던 USER라는 테이블에서 INSTALLER Role을 가진 사람만 조회를 하려고 했지만 그렇게 되지 않았고, INSTALLER의 ID가 5번이라면 5번 안쪽 ID들만 조회가 된는게 문제였습니다.

    • 디버깅을 하면서 추측한...문제는 현재 FMSUser에 들어있는 roles라는 클래스 변수는 Role타입인 List값입니다. 그래서 WHERE 절에서 Role객체를 꺼내와서 Role.name = 'INSTALLER' 이런식으로 표현이 되어야하는데 SQL 에서는 작성이 되고 데이터도 표출이 잘 된다.
      2021-12-23-4-48-12.png

    • 질문을 하고 피드백을 받은 것 중 하나가 JPQL은 JAVA에서 객체를 SQL로 다룰 수 있도록 한거지 SQL이 아니기 때문에 '만능' 이 아니야 라고 말씀을 하셨고 굳이 조건절을 위해서 SQL에서 씨름을 하지말고 객체를 받아서 Service단에서 조건절을 해주는건 어때? 라고 피드백을 받았습니다. 뭔가 제일 아... 하게 되는 부분이어서 무릎을 탁! 쳤네요..!

    • 그래서 다음은 수정한 쿼리이고

      @Query
              (
                  "SELECT new com.imaslab.fms.dtos.car.OnsiteServiceReceiverManageDTO(fu.id, fu.name, fu.phone, fc.name, rs.id, rs.name, COALESCE(orm.onsiteCancel, false ) , COALESCE(orm.onsiteWaiting, false ) , COALESCE(orm.onsiteConfirmed, false ) , COALESCE(orm.onsiteOperated, false )) " +
                      "FROM FMSUser fu " +
                      "LEFT JOIN OnsiteReceiverManage orm ON orm.fmsUser.id = fu.id " +
                      "LEFT JOIN FMSCompany fc ON fc.id = fu.companyId " +
                      "INNER JOIN fu.roles rs " +
                      "WHERE coalesce(:userList, NULL) IS NULL OR (fu.id IN :userList) " +
                      "ORDER BY orm.updatedAt desc "
              )
          List<OnsiteServiceReceiverManageDTO> findReceiverManage(List<Long> userList);

      다음은 Service입니다.

      fmsUserRepository.findReceiverManage(StringToLongListSearchUsers.size() == 0 ? null : StringToLongListSearchUsers)
                                       .stream()
                                       .filter(onsiteServiceReceiverManageDTO -> onsiteServiceReceiverManageDTO.getInfoDTO().getRoleName().equals(role))
                                       .collect(Collectors.toList());

결론

  • 이 글의 제목에 내용이 스포가 되었지만 JPQL은 만능이 아니고 우리는 객체를 다룰 수 있으니 유연하게 사고하자.

'Spring > JPA' 카테고리의 다른 글

Bulk Excel Data Insert 1부  (0) 2022.06.20
Java8 Stream 특정 Key 중복제거  (0) 2022.05.30
[Spring JPA] 즉시로딩과 지연로딩  (0) 2021.06.28
Spring JPA [상속관계 맵핑]  (0) 2021.06.17
[Spring JPA] 다양한 연관관계 매핑  (0) 2021.04.19