본문 바로가기
  • 실행력이 모든걸 결정한다
Spring 사전 준비/JPA Hibernate

[JPA] 네이티브 SQL

by 김코더 김주역 2022. 8. 11.
반응형

1. 네이티브 SQL의 소개와 필요성

- 다양한 이유로 JPQL을 사용할 수 없는 상황에서 특정 DB에 종속적인 SQL을 직접 사용할 수 있도록 하는 기능이다.

- JPQL이 아닌 SQL을 이용해도 영속성 컨텍스트에 접근할 수 있는 방법을 제공한다.★

- 특정 DB에 종속적인 기능을 JPA가 해결하는 예로는 다음과 같은 것들이 있다.

  • 특정 DB에서만 사용하는 함수 : 네이티브 SQL 함수를 사용하는 방법, Hibernate가 방언에 정의해둔 함수를 사용하는 방법, 함수를 직접 정의하는 방법이 있다.
  • 특정 DB에서만 지원하는 SQL 쿼리 힌트 : Hibernate를 포함한 몇몇 JPA 구현체들이 지원한다.
  • 특정 DB에서만 지원하는 문법 : 너무 종속적인 SQL 문법은 지원하지 않으므로 네이티브 SQL를 사용해야 한다.
  • 인라인뷰(From절 서브쿼리), INTERSECT, UNION : Hibernate는 지원하지 않지만 일부 JPA 구현체들이 지원한다.
  • 스토어드 프로시저 : JPQL에서 호출 가능하다.

- 네이티브 SQL은 상대적으로 후순위로 고려되는 기술이다.

표준 JPQL > JPA 구현체 > 네이티브 SQL > MyBatis 또는 JdbcTemplate 혼용

 

 

 

2. 네이티브 쿼리 API

- EntityManager의 createNativeQuery() 메소드를 사용한다.

- JPQL과 달리 위치 기반 파라미터만 지원한다.

※ Hibernate는 이름 기반 파라미터도 지원하기 때문에 Hibernate에서는 이름 기반 파라미터로 변경해도 동작한다.

 

1) 엔티티 조회

public Query createNativeQuery(string sql, Class resultClass);

- 첫 번째 파라미터에는 SQL문을 입력하고 두 번째 파라미터는 조회할 엔티티 클래스 타입을 지정한다. 타입 정보를 주었더라도 반환 타입은 Query다.

사용 예시

String sql = "SELECT ID, AGE, NAME, TEAM_ID FROM MEMBER WHERE AGE < ?1"; // 파라미터는 ?로 남겨둔다.
List<Member> result = em.createNativeQuery(sql, Member.class)
    .setParameter(1, 30)
    .getResultList();

 

 

2) 값 조회

public Query createNativeQuery(string sql);

- 결과 타입을 정의할 수 없는 경우에는 단순히 값으로 조회할 수 있다.

- 엔티티 클래스가 지정되지 않았기 때문에 조회한 값들은 Object[] 배열에 담긴다.

- 조회 결과가 영속성 컨텍스트에 저장되지 않는다.

사용 예시

String sql = "SELECT ID, AGE, NAME, TEAM_ID FROM MEMBER WHERE AGE < ?1"; // 파라미터는 ?로 남겨둔다.
List<Object[]> result = em.createNativeQuery(sql)
    .setParameter(1, 30)
    .getResultList();

 

 

3) 결과 매핑 사용

public Query createNativeQuery(string sql, String resultSetMapping);

- 엔티티와 값을 함께 조회하는 경우처럼 매핑이 복잡해지면 결과 매핑을 사용하는 것이 좋다.

- @SqlResultSetMapping을 정의해서 결과 매핑을 사용할 수 있다. @SqlResultMapping의 entities 속성에 매핑할 엔티티 클래스 또는 필드들을 지정하고, columns 속성에는 테이블에 존재하지 않는 프로젝션 값들을 지정할 수 있다.

 

@SqlResultSetMapping 정의 예시

@SqlResultSetMapping(
    name = "OrderResults", // 결과 매핑 이름
    entities = {
        @EntityResult(entityClass = com.acme.Order.class, fields={
            @FieldResult(name="id", column="order_id"),
            @FieldResult(name="quantity", column="order_quantity"),
            @FieldResult(name="item", column="order_item")
        })
    },
    columns = {
        @ColumnResult(name = "item_name")
    }
)

- @FieldResult는 컬럼명과 필드명을 직접 매핑하는 어노테이션이다. @FieldResult를 한 번이라고 사용하면 전체 필드를 @FieldResult로 매핑해야 하고, 여러 엔티티를 조회할 때 컬럼명이 중복될 때도 @FieldResult를 사용해야 한다.

※ @FieldResult.name : 결과를 받을 필드명

※ @FieldResult.column : 결과 컬럼명

- @ColumnResult.name : 결과 컬럼명

 

SQL문 작성 예시

Query q = em.createNativeQuery(
    "SELECT o.id AS order_id, " +
        "o.quantity AS order_quantity, " +
        "o.item AS order_item, " +
        "i.name AS item_name, " +
    "FROM Order o, Item i " +
    "WHERE (order_quantity > 25) AND (order_item = i.id)"
, "OrderResults");

 

 

 

3. Named 네이티브 SQL

- @NamedNativeQuery를 사용하여 네이티브 SQL을 등록한다.

 

1) @NamedNativeQuery 선언 예시

@Entity
@NamedNativeQuery(
    name="Member.memberAgeGt",
    query="SELECT ID, AGE, NAME, TEAM_ID "+
        "FROM MEMBER WHERE AGE > ?1",
    resultClass = Member.class
)
public class Member {...}

 

추가 옵션

- @NamedNativeQuery.resultSetMapping : 단순히 엔티티를 조회하는 대신에 결과 매핑을 사용하고 싶다면, @NamedNativeQuery.resultClass 대신에 @NamedNativeQuery.resultSetMapping을 사용하면 된다. 조회 결과를 매핑할 대상의 @SqlResultSetMapping.name을 작성하면 된다.

@Entity
@SqlResultSetMapping(
    name = "memberAgeGt",
    ...
)
@NamedNativeQuery(
    name="Member.memberAgeGt",
    query="SELECT ID, AGE, NAME, TEAM_ID "+
        "FROM MEMBER WHERE AGE > ?1",
    resultSetMapping = "memberAgeGt"
)
public class Member {...}

- @NamedNativeQuery.hints : JPA 구현체에 종속적인 힌트

 

 

2) @NamedNativeQuery 사용 방법

- @NamedNativeQuery에 지정한 name을 작성하면 된다.

List<Object[]> resultList = em.createNamedQuery("Member.memberAgeGt")
    .setParameter(1, 30)
    .getResultList();

 

 

3) 여러 @NamedNativeQuery 선언하기

- @NamedNativeQueries 안에 여러 @NamedNativeQuery들을 선언하면 된다.

@NamedNativeQueries({
    @NamedNativeQuery(...),
    @NamedNativeQuery(...)
})

 

참고) Named 쿼리 추가 예제

https://cheolhojung.github.io/posts/record/jpa-named-native-query-result-map.html

 

 

 

4. 네이티브 SQL을 XML에 정의하기

- 네이티브 SQL은 보통 복잡한 쿼리를 작성할 때 쓰이기 때문에 라인 수가 많은 경향이 있다. 게다가 자바는 멀티라인 문자열을 지원하지 않기 때문에 어노테이션 설정보다는 XML 설정이 더 유리할 수 있다.

- xml에 정의할 때는 <named-native-query>를 먼저 정의하고 <sql-result-set-mapping>을 정의해야 한다.

- 어노테이션 방식의 속성명과 XML 방식의 속성명이 유사하기 때문에 의미하는 바는 직관적으로 알 수 있을 것이다.

 

예시

<ormMember.xml>

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="htt://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
    <named-native-query name="Member.memberWithOrderCountXml"
        result-set-mapping="memberWithOrderCountResultMap">
        <query>
            <![CDATA[
                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)
                ON M.ID = I.ID
            ]]>
        </query>
    </named-native-query>

    <sql-result-set-mapping name="memberWithOrderCountResultMap">
        <entity-result entity-class="com.example.demo.dto.Member"/>
        <column-result name="ORDER_COUNT"/>
    </sql-result-set-mapping>

</entity-mappings>

 

 

참고로, 매핑 파일을 JPA가 인식하도록 META-INF/persistence.xml에 매핑 파일을 추가하는 것을 잊지 말자

<persistence-unit name="hello">
    <mapping-file>META-INF/ormMember.xml</mapping-file>
    ...
>

 

 

 

● 참고자료 : 자바 ORM 표준 JPA 프로그래밍

 

반응형

'Spring 사전 준비 > JPA Hibernate' 카테고리의 다른 글

[JPA] JPQL 최적화  (0) 2022.08.14
[JPA] 스토어드 프로시저  (0) 2022.08.14
[JPA] QueryDSL  (0) 2022.08.10
[JPA] JPQL의 작성  (0) 2022.08.04
[JPA] 값 타입 컬렉션  (0) 2022.08.03

댓글