JPQL은 표준 SQL이 지원하는 대부분의 문법과 SQL함수를 지원한다.
근데 특정 DB의 방언과 같은 종속적 기능은 지원하지 않는다.
종속적인 기능을 지원하는 방법은
네이티브 SQL을 사용하면 엔티티를 조회할 수 있고, JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.
네이티브 쿼리 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_id
는 Member
엔티티로 매핑을 시키고 order_count
는 단순 칼럼으로 매핑했다.
이렇게 여러 컬럼들을 매핑해서 추출할 수 있다.
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로 하지만 한방쿼리가 적절하게 필요할때만 사용하도록 해야할듯? 싶다. 😅
아직 실무에서 제대로 사용하지 않아서 이런 실무에서의 타협점은 점차 늘려가야 될것으로 보인다.