JPA - NativeQuery ( SQL ) 네이티브 SQL 사용하기!

2021. 4. 17. 02:17 Spring Data/Spring Data JPA

JPA - NativeQuery ( SQL ) 네이티브 SQL 사용하기!

JPA는 SQL이 지원하는 대부분의 문법과 SQL 함수들을 지원하지만 특정 데이터베이스에 종속적인 기능은 잘 지원하지 않는다. 하지만 때론 특정 데이터베이스에 종속적인 기능이 필요할 수도 있다. 다양한 이유로 JPQL을 사용할 수 없을 때, JPA는 SQL을 직접 사용할 수 있는 기능을 제공하는데 이것을 네이티브 SQL(네이티브쿼리)라고 한다. 즉, 사용자가 직접 데이터베이스에 날리는 쿼리를 작성하는 것이다. 그렇다면 JPA가 지원하는 네이티브 SQL과 JDBC API를 직접 사용하는 것에는 어떤 차이가 있냐? 그것은 바로 네이트브 쿼리는 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다는 것이다!

 

Native Query API

//결과 타입 정의
public Query createNativeQuery(String sqlString, Class resultClass) {}
//결과 타입을 정의할 수 없을 때
public Query createNativeQuery(String sqlString) {}
//엔티티 타입이 아닌 결과 값일때
public Query createNativeQuery(String sqlString
                        ,String resultSetMapping) {}

 

 이렇게 세가지의 오버로딩된 메소드를 사용한다.

 

/*
 * 네이티브쿼리는 위치기반 파라미터 바인딩만 지원한다.(구현체중 일부는 이름기반 파라미터 바인딩도 지원함)
 */
public void nativeQuery() {
        String sql = "SELECT MEMBER_JPQL_ID,USERAGE,USERNAME,TEAM_ID FROM MEMBER_JPQL WHERE USERAGE> ?";
        
        Query nativeQuery = em.createNativeQuery(sql,MemberJPQL.class).setParameter(1, 35);
        
        List<MemberJPQL> resultList = nativeQuery.getResultList();
        
        System.out.println("====================nativeQuery=====================");
        
        for(MemberJPQL m : resultList) {
                System.out.println(m.toString());
        }
        
        System.out.println("====================nativeQuery=====================");
}

 

리턴타입이 엔티티일 경우이다. em.createNativeQuery(SQL문장,리턴타입클래스)로 Query객체를 만들어낸다. 위의 예제를 보면 JPQL과 다를 것은 쿼리를 직접작성하냐 안하냐의 차이지 대부분의 로직은 비슷하다.(JPA가 자동으로 생성해주는 SQL를 사용자가 직접 작성한 것뿐 더이상의 차이는 거의없다.)

그리고 Native Query에서는 반환타입이 명시되어도 Query 객체로 반환받는다는 점을 유의하자.

 

다음은 결과 매핑 사용 예제이다.

 

@Entity
@Table(name = "MEMBER_JPQL")
//@Getter
//@Setter
@ToString
@SqlResultSetMapping(name = "memberWithOrderCount",
                                        entities = {@EntityResult(entityClass = MemberJPQL.class)},
                                        columns = {@ColumnResult(name = "ORDER_COUNT")})
public class MemberJPQL {....}

 

/*
 * ResultMapping
 */
public void nativeQuery2() {
        String sql = "SELECT M.MEMBER_JPQL_ID AS ID,USERAGE AS AGE,USERNAME,TEAM_ID,I.ORDER_COUNT "
                                + "FROM MEMBER_JPQL M JOIN  "
                                                + "(SELECT IM.MEMBER_JPQL_ID, COUNT(*) AS ORDER_COUNT "
                                                + "FROM ORDER_JPQL O, MEMBER_JPQL IM "
                                                + "WHERE O.MEMBER_JPQL_ID = IM.MEMBER_JPQL_ID) I "
                                + "ON M.MEMBER_JPQL_ID = I.MEMBER_JPQL_ID";
                                
        Query nativeQuery = em.createNativeQuery(sql,"memberWithOrderCount");
        
        List<Object[]> resultList = nativeQuery.getResultList();
        
        System.out.println("====================nativeQuery2=====================");
        
        for(Object[] m : resultList) {
                System.out.println(m[0].toString());
                System.out.println(m[1]);
        }
        
        System.out.println("====================nativeQuery2=====================");
}

 

특별한 것은 없다. 엔티티클래스에 네이티브 쿼리로 받을 반환 타입을 명시해주었고, 네이티브 쿼리를 사용한 쪽에서는 반환타입을 엔티티클래스에 작성한 반환타입 이름 값을 문자열로 넘겨주었다. 그래서 반환된 Object[]에서 Object[0]은 회원엔티티로 매핑되고, Object[1]은 스칼라타입으로 ORDER_COUNT를 담고있다.

어노테이션을 보면 속성 값이 entities,columns로 된것으로 보아 여러개의 엔티티,칼럼을 매핑할 수 있다. 그리고 @SqlResultSetMappings 어노테이션이 있기 때문에 여러개의 매핑타입을 만들 수 있다.

 

 

그런데 대부분 엔티티 클래스의 id클래스의 필드명은 "id"로 거의 동일하게 하는 경우도 많다. 그래서 조회를 하면 칼럼명이 중복나는 경우가 생기게 된다. 그때는 필드매핑까지 포함하여서 조회가능하다.

 

@Entity
@Table(name = "MEMBER_JPQL")
//@Getter
//@Setter
@ToString
@SqlResultSetMappings({
        @SqlResultSetMapping(name = "memberWithOrderCount",
                        entities = {@EntityResult(entityClass = MemberJPQL.class)},
                        columns = {@ColumnResult(name = "ORDER_COUNT")}
        )
        ,@SqlResultSetMapping(name = "memberInfo",
                        entities = {
                                        @EntityResult(entityClass=MemberJPQL.class,
                                        fields = {
                                                        @FieldResult(name = "id",column = "member_id")
                                                        ,@FieldResult(name = "age",column = "client_age")
                                        })
                        }
        )
        
})
public class MemberJPQL {...}

 

public void nativeQuery3() {
        String sql = "select MEMBER_JPQL_ID as member_id,USERAGE as client_age "
                        + "from member_jpql where USERNAME = '여성게1'";
        
        Query nativeQuery = em.createNativeQuery(sql,"memberInfo");
        
        List<MemberJPQL> resultList = nativeQuery.getResultList();
        
        System.out.println("====================nativeQuery3=====================");
        
        for(MemberJPQL m : resultList) {
                System.out.println(m.toString());
        }
        
        System.out.println("====================nativeQuery3=====================");
}

 

부득이하게 조회할 컬럼명을 별칭으로 주었을 경우, 엔티티 클래스와 매핑이 되지 않는다. 그럴때 엔티티클래스에 필드매핑을 이용한다.

 

@FieldResult(name = "id",column = "member_id")

 

name이 엔티티클래스의 필드명이고, column이 조회하였을 때 반환되는 컬럼명이다.

 

 

마지막으로 이전에 다루었던 정적쿼리(@NamedQuery)처럼 네이티브 쿼리로 정적쿼리처럼 엔티티클래스에 정의할 수 있다.

 

▶︎▶︎▶︎JPA - @NamedQuery , 정적 쿼리

 

@Table(name = "MEMBER_JPQL")
//@Getter
//@Setter
@ToString
@SqlResultSetMappings({
        @SqlResultSetMapping(name = "memberWithOrderCount",
                        entities = {@EntityResult(entityClass = MemberJPQL.class)},
                        columns = {@ColumnResult(name = "ORDER_COUNT")}
        )
        ,@SqlResultSetMapping(name = "memberInfo",
                        entities = {
                                        @EntityResult(entityClass=MemberJPQL.class,
                                        fields = {
                                                        @FieldResult(name = "id",column = "member_id")
                                                        ,@FieldResult(name = "age",column = "client_age")
                                        })
                        }
        )
        
})
@NamedNativeQueries({
        @NamedNativeQuery(name = "Member.memberSQL",
                                                query = "select member_jpql_id,userage,username,team_id "
                                                                + "from member_jpql "
                                                                + "where username = ?",
                                                resultClass=MemberJPQL.class)
        ,@NamedNativeQuery(name = "Member.memberSQL2",
                        query = "select MEMBER_JPQL_ID as member_id,USERAGE as client_age "
                                        + "from member_jpql where USERNAME = ?",
                        resultSetMapping = "memberInfo")
})
public class MemberJPQL {}

 

public void nativeQuery4() {
        
        Query nativeQuery = em.createNamedQuery("Member.memberSQL",MemberJPQL.class)
                        .setParameter(1, "여성게1");
        
        List<MemberJPQL> resultList = nativeQuery.getResultList();
        
        System.out.println("====================nativeQuery4=====================");
        
        for(MemberJPQL m : resultList) {
                System.out.println(m.toString());
        }
        
        System.out.println("====================nativeQuery4=====================");
}
public void nativeQuery5() {
        
        Query nativeQuery = em.createNamedQuery("Member.memberSQL2")
                        .setParameter(1, "여성게1");
        
        List<MemberJPQL> resultList = nativeQuery.getResultList();
        
        System.out.println("====================nativeQuery5=====================");
        
        for(MemberJPQL m : resultList) {
                System.out.println(m.toString());
        }
        
        System.out.println("====================nativeQuery5=====================");
}

 

 

em.createNativeQuery()에서 다루던 것을 어노테이션으로 다 가져다 놓았다고 보면된다. 그리고 조금 특이한 것은 @NamedQuery와 같이 em.createNamedQuery()를 사용한다는 것이다.



출처: https://coding-start.tistory.com/101?category=781616 [코딩스타트]