JPA

[JPA] 객체 지향 쿼리 언어 - Native SQL

리승자이 2022. 8. 5. 23:42
728x90

네이티브 SQL

JPQL은 표준 SQL이 지원하는 대부분의 문법과 SQL함수를 지원한다.
근데 특정 DB의 방언과 같은 종속적 기능은 지원하지 않는다.

  • 특정 DB만 지원하는 함수, 문법, SQL 쿼리 힌트
  • 인라인 뷰(from절 서브쿼리), UNION, INTERSECT
  • 스토어드 프로시저

종속적인 기능을 지원하는 방법은

  • 특정 DB만 사용하는 함수
    • 특정 JPQL에서 네이티브 SQL 함수를 호출할 수 있다.
    • Hibernate는 DB 방언에 각 DB에 종속적 함수를 정의했다. 그리고 직접 호출할 함수를 정의하기도 가능하다.
  • 특정 DB만 지원하는 힌트
    • Hibernate를 포함한 몇몇 JPA구현체가 지원한다.
  • 인라인 뷰(from절 서브쿼리), UNION, INTERSECT
    • JPA구현체들이 지원한다. Hibernate는 지원 ❌
  • 스토어드 프로시저
    • JPQL에서 호출 가능
  • 특정 DB만 지원하는 문법
    • 너무 유니크한 쿼리 문법은 지원하지 않는데, 이때 네이티브 SQL사용. 근데 이걸 사용할까...?

네이티브 SQL을 사용하면 엔티티를 조회할 수 있고, JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.

네이티브 SQL 사용

네이티브 쿼리 API는 3가지가 있다.

  • 결과 타입을 정의
  • 결과 타입을 정의할수 없을 때
  • 결과 매핑 사용

엔티티 조회

    @Test
    @DisplayName("네이티브 SQL 엔티티 조회")
    void nativeQueryTest() {
        String sql = "select id, name, price from item where price > ?";
        Query nativeQuery = em.createNativeQuery(sql, Item.class).setParameter(1, 100);

        List<Item> items = nativeQuery.getResultList();
    }

jdbc 사용할때와 똑같은 느낌이 든다.
근데 가장 중요한점은
SQL만 직접 사용할 뿐, JPQL을 사용할 때와 같다. 조회한 엔티티도 영속성 컨텍스트에서 관리 된다.

값 조회

값으로 조회하려면 엔티티 조회처럼 class를 같이 넣어주는게 아니라
em.createNativeQuery(sql) 를 사용하면 된다.
대신 이때 nativeQuery.getResultList() 는 Object 배열을 반환하므로
List<Object[]> 로 반환을 받아야한다.
더욱 더 JDBC같이 생겼다.

결과 매핑 사용

결과 매핑을 사용하면 엔티티 자체에 너무 많은 어노테이션 설정을 해야되므로 보편적으로 사용하지 않을 것 같다는 나의 생각이 들어있다.
그래도 정리를 해보도록 하겠다.

String sql = "select M.ID, AGE, NAME, TEAM_ID, I.ORDER_COUNT FROM MEMBER M " +
"LEFT JOIN (SELECT IM.ID, COUNT(*) AS ORDER_COUNT FROM ORDERS O, MEMBER IM " +
"WHERE O.MEMBER_ID = IM.ID) I ON M.ID = I.ID";

Query nativeQuery = em.createNativeQuery(sql, "memberWithOrderCount");
List<Object[]> members = nativeQuery.getResultList();

아래는 매핑 정의 코드이다.

@Entity
@SqlResultSetMapping(name = "memberWithOrderCount",
    entities = {@EntityResult(entityClass = Member.class) },
    columns = {@ColumnResult(name = "ORDER_COUNT")}
}
public class Member {...}

id, age, name, team_idMember 엔티티로 매핑을 시키고 order_count 는 단순 칼럼으로 매핑했다.
이렇게 여러 컬럼들을 매핑해서 추출할 수 있다.

@NamedNativeQuery

Named 네이티브 SQL을 사용하여 정적 SQL도 작성이 가능하다.
엔티티 클래스에

@NamedNativeQuery(
    name = "Member.memberSQL",
    query = "select 조회 쿼리문",
    resultClass = Member.class
)

로 등록해주고 사용하고자 하는 곳에서

TypedQuery<Member> nativeQuery = em.createNamedQuery("@NamedNativeQuery의 name", Member.class)
//파라미터가 있을 때
.setParameter();

네이티브 SQL은 휴먼에러를 발생할 확률이 QueryDSL보다 굉장히 높을 것으로 예상한다.
그래서 웬만하면 QueryDSL로 하지만 한방쿼리가 적절하게 필요할때만 사용하도록 해야할듯? 싶다. 😅
아직 실무에서 제대로 사용하지 않아서 이런 실무에서의 타협점은 점차 늘려가야 될것으로 보인다.

728x90