네이티브 쿼리 (Native Query)

JOY🌱·2023년 4월 11일
0

🐸 JPA

목록 보기
6/8
post-thumbnail

💁‍♀️ 네이티브 쿼리란,
SQL을 개발자가 직접 정의해서 사용할 수 있도록 해주는 수동모드
JPQL은 데이터베이스들이 따로 지원하는 것들에 있어 모든 것을 SQL로 자동으로 바꿔주지 않음 (인라인 뷰, UNION, INTERSECT 등등)
쉽게 말해, 어떠한 다양한 이유로 JPQL을 사용할 수 없는 경우나 SQL쿼리를 최적화 해서 데이터 베이스의 성능을 향상시킬 때 JPA는 Native SQL을 통해 SQL을 직접 사용할 수 있는 기능을 제공


🙋‍ JDBC API와의 차이점
직접 SQL을 작성하는 JDBC API와는 달리 네이티브 SQL은 JPA의 영속성 컨텍스트의 기능을 그대로 사용 가능


🙋‍ 네이티브 쿼리 API의 사용

  • 결과 타입 정의
    public Query createNativeQuery(String sqlString, Class resultClass);
  • 결과 타입을 정의할 수 없을 때
    public Query createNativeQuery(String sqlString);
  • 결과 매핑 사용
    public Query createNativeQuery(String sqlString, String resultSetMapping);

👀 Simple

✅ 결과 타입 정의

@Test
public void 결과_타입을_정의한_네이티브_쿼리_사용_테스트() {
    	
    // given
    int menuCodeParameter = 144;
    	
    // when
    /* 우리가 사용하는 DBMS의 고유한 SQL 문법을 작성. 위치 기반 파라미터로만 사용이 가능!! */
    String query = "SELECT MENU_CODE, MENU_NAME, MENU_PRICE, CATEGORY_CODE, ORDERABLE_STATUS "
    			 + "FROM TBL_MENU WHERE MENU_CODE = ?";
    	
    /* 일부 컬럼만 조회하는 것은 불가능 */
//  String query = "SELECT MENU_CODE, MENU_NAME, MENU_PRICE, CATEGORY_CODE, "
//				 + "FROM TBL_MENU WHERE MENU_CODE = ?";
    	
    /* 주의 !!!
     * 모든 컬럼값을 매핑하는 경우에만 타입을 특정(ex: Manu.class)할 수 있음. 일부 컬럼만 조회하고 싶은 경우는 Object[] 또는 스칼라 값을 별도로 담을 클래스를 정의해서 사용해야함 */
    Query nativeQuery = entityManager.createNativeQuery(query, Menu.class)
    								 .setParameter(1, menuCodeParameter); /* 위치 기반 */
    Menu foundMenu = (Menu) nativeQuery.getSingleResult(); /* 다운캐스팅 */
    	
    // then
    assertNotNull(foundMenu);
    assertTrue(entityManager.contains(foundMenu)); /* 영속성 컨텍스트에서 관리하는 객체인지 확인 (엔티티로 처리했기 때문에 True) */
    System.out.println(foundMenu);

}

✅ 결과 타입을 정의할 수 없을 때

@Test
public void 결과_타입을_정의할_수_없는_경우_조회_테스트() {
    	
    // when
    String query = "SELECT MENU_NAME, MENU_PRICE FROM TBL_MENU";
    List<Object[]> menuList = entityManager.createNativeQuery(query) /* 엔티티 타입이 아닌 경우, 타입 작성 XXX (작성 시 오류) */
    									   .getResultList();
    /* 결과 타입을 특정하는 것 자체가 불가능. 엔티티로 매핑 시킬 경우에만 클래스 타입을 입력 */
//  List<Object[]> menuList = entityManager.createNativeQuery(query, Object[].class).getResultList();
    	
    //then
    assertNotNull(menuList);
    menuList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print(col + " "));
    	System.out.println();
    });

}

✅ 결과 매핑 사용

@SqlResultSetMappings @SqlResultSetMapping @EntityResult @ColumnResult @FieldResult

@Entity(name="section01_category")
@Table(name="TBL_CATEGORY")
/* MENU_COUNT는 가공된 컬럼이므로 따로 매핑 처리를 해줘야함 */
@SqlResultSetMappings(
		value= {
				/* 1. @Column으로 매핑 설정이 되어있는 경우 - 자동 엔티티 매핑 */
				@SqlResultSetMapping(
					name = "categoryCountAutoMapping",							// 결과 매핑 이름
					entities = {@EntityResult(entityClass = Category.class)},	// @EntityResult를 사용해서 엔티티를 결과로 매핑
					columns = {@ColumnResult(name = "MENU_COUNT")}				// @ColumnResult를 사용해서 컬럼을 결과로 매핑
				),
				
				/* 2. 매핑 설정을 수동으로 해주는 경우 - 필드 위의 @Column은 생략 가능 */
				@SqlResultSetMapping(
					name = "categoryCountManualMapping",
					entities = {
								@EntityResult(entityClass = Category.class, fields = {
										@FieldResult(name = "categoryCode", column = "CATEGORY_CODE"),
										@FieldResult(name = "categoryName", column = "CATEGORY_NAME"),
										@FieldResult(name = "refCategoryCode", column = "REF_CATEGORY_CODE")
								})
					},
					columns = {@ColumnResult(name = "MENU_COUNT")}
				)
		}
)
public class Category {

	@Id
	@Column(name="CATEGORY_CODE")
	private int categoryCode;
    
	@Column(name="CATEGORY_NAME")
	private String categoryName;
    
	@Column(name="REF_CATEGORY_CODE")
	private Integer refCategoryCode;
    
    /* 기본 셋팅 */
}

◼ 자동 결과 매핑

@Test
public void 자동_결과_매핑을_사용한_조회_테스트() {
    	
    //when
    String query = "SELECT"
                 + " A.CATEGORY_CODE, A.CATEGORY_NAME, A.REF_CATEGORY_CODE, NVL(V.MENU_COUNT, 0) MENU_COUNT"
                 + " FROM TBL_CATEGORY A"
                 + " LEFT JOIN (SELECT COUNT(*) AS MENU_COUNT, B.CATEGORY_CODE "
                 + "            FROM TBL_MENU B"
                 + "            GROUP BY B.CATEGORY_CODE) V ON (A.CATEGORY_CODE = V.CATEGORY_CODE)"
                 + " ORDER BY 1";
    	
    Query nativeQuery = entityManager.createNativeQuery(query, "categoryCountAutoMapping"); /* Category 엔티티의 ResultSet안의 name 속성 */
    List<Object[]> categoryList = nativeQuery.getResultList();
        
    //then
    assertTrue(entityManager.contains(categoryList.get(0)[0])); /* categoryList 안의 첫 번째 객체의 0번 인덱스가 영속성 컨텍스트에서 관리가 되는지 확인 */
 	assertNotNull(categoryList);
   	categoryList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print(col + " "));
    	System.out.println();
    });
}

◼ 수동 결과 매핑

@Test
public void 수동_결과_매핑을_사용한_조회_테스트() {
    	
    //when
    String query = "SELECT"
                 + " A.CATEGORY_CODE, A.CATEGORY_NAME, A.REF_CATEGORY_CODE, NVL(V.MENU_COUNT, 0) MENU_COUNT"
                 + " FROM TBL_CATEGORY A"
                 + " LEFT JOIN (SELECT COUNT(*) AS MENU_COUNT, B.CATEGORY_CODE "
                 + "            FROM TBL_MENU B"
                 + "            GROUP BY B.CATEGORY_CODE) V ON (A.CATEGORY_CODE = V.CATEGORY_CODE)"
                 + " ORDER BY 1";
    	
    Query nativeQuery = entityManager.createNativeQuery(query, "categoryCountManualMapping"); /* Category 엔티티의 ResultSet안의 name 속성 */
   List<Object[]> categoryList = nativeQuery.getResultList();
        
    //then
    assertTrue(entityManager.contains(categoryList.get(0)[0])); /* categoryList 안의 첫 번째 객체의 0번 인덱스가 영속성 컨텍스트에서 관리가 되는지 확인 */
  	assertNotNull(categoryList);
    categoryList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print(col + " "));
    	System.out.println();
    }); 
}

👀 Named

🙋‍ 네이티브 SQL도 JPQL처럼 @NamedNativeQuery를 사용해 정적 SQL을 만들어두고 사용 가능

✅ NamedNativeQuery

@NamedNativeQueries @NamedNativeQuery

/* Category 엔티티 */
@Entity(name="section02_category")
@Table(name="TBL_CATEGORY")
@SqlResultSetMapping(name = "categoryCountAutoMapping2",			// section1의 매핑 이름과 다른 이름을 주어야 함
        entities = {@EntityResult(entityClass = Category.class)},
        columns = {@ColumnResult(name = "MENU_COUNT")}
)
@NamedNativeQueries(
        value = {
            @NamedNativeQuery(
                    name = "Category.menuCountOfCategory",
                    query = "SELECT"
                            + " A.CATEGORY_CODE, A.CATEGORY_NAME, A.REF_CATEGORY_CODE, NVL(V.MENU_COUNT, 0) MENU_COUNT"
                            + " FROM TBL_CATEGORY A"
                            + " LEFT JOIN (SELECT COUNT(*) AS MENU_COUNT, B.CATEGORY_CODE"
                            + "            FROM TBL_MENU B\n"
                            + "            GROUP BY B.CATEGORY_CODE) V ON (A.CATEGORY_CODE = V.CATEGORY_CODE)"
                            + " ORDER BY 1",
                    resultSetMapping = "categoryCountAutoMapping2" // 위의 @SqlResultSetMapping의 name 속성과 동일하게 작성
            )
        }
)
public class Category {

}
@Test
public void NamedNativeQuery를_이용한_조회_테스트() {
    	
   	// when
    Query nativeQuery = entityManager.createNamedQuery("Category.menuCountOfCategory"); /* Category 엔티티의 @NamedNativeQuery의 name 속성 */
    List<Object[]> categoryList = nativeQuery.getResultList();
        
    //then
    assertTrue(entityManager.contains(categoryList.get(0)[0]));
    assertNotNull(categoryList);
    categoryList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print(col + " "));
    	System.out.println();
    });
}

💻 Mini Console

Category{categoryCode=4, categoryName='한식', refCategoryCode=1} 6 
Category{categoryCode=5, categoryName='중식', refCategoryCode=1} 3 
Category{categoryCode=6, categoryName='일식', refCategoryCode=1} 4 
Category{categoryCode=7, categoryName='퓨전', refCategoryCode=1} 5 
...

✅ NamedNativeQuery (XML)

category-query.xml

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
	<named-native-query name="Category.menuCountOfCategoryXML" result-set-mapping="categoryCountXmlMapping">
		<query>
			SELECT
			       A.CATEGORY_CODE
			     , A.CATEGORY_NAME
			     , A.REF_CATEGORY_CODE
			     , NVL(V.MENU_COUNT, 0) MENU_COUNT
			  FROM TBL_CATEGORY A
			  LEFT JOIN (SELECT COUNT(*) AS MENU_COUNT
			                  , B.CATEGORY_CODE
			               FROM TBL_MENU B
			              GROUP BY B.CATEGORY_CODE
			            ) V ON (A.CATEGORY_CODE = V.CATEGORY_CODE)
			 ORDER BY 1
		</query>
	</named-native-query>
	
	<sql-result-set-mapping name="categoryCountXmlMapping">
		<entity-result entity-class="com.greedy.section02.named.Category"/>
		<column-result name="MENU_COUNT"/>
	</sql-result-set-mapping>
	
</entity-mappings>

persistence.xml

@Test
public void xml_NamedNativeQuery를_이용한_조회_테스트() {
    	
    // when
    Query nativeQuery = entityManager.createNamedQuery("Category.menuCountOfCategoryXML"); /* xml파일의 named-native-query의 name 속성 */
    List<Object[]> categoryList = nativeQuery.getResultList();
        
    //then
    assertTrue(entityManager.contains(categoryList.get(0)[0]));
    assertNotNull(categoryList);
    categoryList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print("[" + col + "]"));
    	System.out.println();
    });
}

💻 Mini Console

[Category{categoryCode=4, categoryName='한식', refCategoryCode=1}][6]
[Category{categoryCode=5, categoryName='중식', refCategoryCode=1}][3]
[Category{categoryCode=6, categoryName='일식', refCategoryCode=1}][4]
[Category{categoryCode=7, categoryName='퓨전', refCategoryCode=1}][5]
...
profile
Tiny little habits make me

0개의 댓글