ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] NativeQuery를 POJO로 mapping하기
    Backend/Springboot 2022. 9. 16. 13:41

    JPA Repository에 있는 데이터를 직접 정의한 class에 바로 담기 위해 고군분투를 해보았다.

    실제로 Entity 형식이 아닌 다른 형식으로 데이터를 바로 담고 싶을 때가 있었다.

     

    즉 NativeQuery를 활용해 SQL로 가공 및 처리하여 필요한 정보만을 추출해 서버단에서는 가공된 데이터만을 받아서 클라이언트에게 전달하는 역할만 하는 것이 더 효율적이라고 판단했기 때문이다.

     

    NativeQuery로 작성한 쿼리를 직접 정의한 class(POJO or DTO)로 불러오기 다음과 같은 에러가 떴다.

     

    ConverterNotFoundException

    org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.semtax.application.data.dto.DashboardDto$Score]

     

    문제를 해결하기 위해 몇 가지 해결방안을 찾아보았다.

     

    •  JPQL Constructor Expressions

    Query문 안에서 특정 POJO의 경로를 지정해두고 select 이하에 POJO 객체를 직접 생성하여 리턴 받는 방법

    하지만 이 같은 시도를 했을 때, 아래와 같은 에러가 떴다.

     

    hibernate.exception.SQLGrammarException

    But, org.hibernate.exception.SQLGrammarException: could not extract ResultSet

     

    즉, 특정 POJO에 해당하는 DB 테이블이 없기도 없고, 테이블의 일부 데이터를 가지고 왔지만 테이블의 ID가 없는 채로 가지고 왔기에 문제가 발생했다. 즉, (@GeneratedValue를 포함하지 않은 채로 결과물 출력)

    그래서 다른 방법을 찾아보았다.

     

    • Projection

    https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections

    위는 공식문서에서 설명하는 Projection 정의다.

     

    Projection의 경우 entity에서 일부 컬럼들만 가지고 오고 싶을 때, 새롭게 interface를 만들어 필요한 속성들을 유용하게 가져올 수 있는 방법이다.

     

    나의 경우 Score Table의 경우에 ScoreId, EssayId, Holistic_Score, Exp_Score, Org_Score, Cont_Score가 있는데 이 중 순수한 점수들만 DTO에 담고 싶었다.

     

    package com.semtax.application.data.dto;
    
    public interface ScoreOnly {
        Integer getHolisticScore();
        Integer getExpScore();
        Integer getOrgScore();
        Integer getContScore();
    }

    그래서 위와 같이 ScoreOnly라는 새로운 인터페이스를 정의하여 ScoreRepository에서 위 인터페이스로 받아왔다.

     

    @Query(value = "select AVG(ss.HOLISTIC_SCORE) as holisticScore,\n" +
            "AVG(ss.EXP_SCORE) as expScore,\n" +
            "AVG(ss.ORG_SCORE) as orgScore,\n" +
            "AVG(ss.CONT_SCORE) as contScore\n" +
            "from (select s.* from score s inner join (select max(ESSAY_ID) as ESSAY_ID  from essay e where e.PROMPT_ID = :prompt_id group by USER_ID) as eu\n" +
            "on s.ESSAY_ID  = eu.ESSAY_ID) as ss;", nativeQuery = true)
    ScoreOnly findByPrompt(String prompt_id);

    이 때, 반복적으로 에러가 떴었는데 stackoverflow를 한참을 찾고 나서 알게 되었다.

     

    If you use SELECT table.column ...  notation always define aliases matching names from entity. For example this code won't work properly (projection will return nulls for each getter):

     

    위에 작성했던 NativeQuery를 보면 최종적으로 select 이후에 as로 entity의 속성과 이름을 맞추어주니 에러 없이 돌아갈 수 있었다.

     

     


    결론 

     

    단순히 하나의 테이블에서 일부 값만 가지고 올 때엔 Projection을 쓰면 손쉽게 데이터 값을 불러올 수 있다. 

    2개 혹은 3개 이상의 여러 테이블을 조인하여 여러 테이블에서 필요한 속성들만을 가지고 올 때에도 유용한지

    확인이 필요하다. (#TODO1)

     

    위와 같은 케이스엔 EntityManager를 활용하여 @NamedNativeQuery와 @sqlresultsetmapping를 주로 사용하는 예제들이 많았다.

     

    EntityManager를 이해하기 위해서는 JPA의 기본적인 하이버네이트, 영속성컨텍스트와 관련한 개념들이 선행되어야 할 것으로 보였다. 이 부분에 대한 추가 학습이 필요하다고 생각한다.

     

     

    또한 어디까지를 서버에서 작업하는 것이 나은지 DB단에서 처리해서 주는게 나은지에 대한 복합적인 판단도 필요해보였다. 기본적인 개념들을 숙지하면서 고민해보면 좋을 것 같았다. 

Designed by Tistory.