
ORM을 쓰면서도 DB 고유 SQL을 그대로 활용하고 싶을 때 쓰는 게 Native query. 복잡한 집계/힌트/특정 함수 등 JPQL로 표현 어려운 쿼리를 그대로 사용하되, 영속성 컨텍스트 연동(엔티티 매핑 시) 이점은 그대로 챙길 수 있음.
// 1) 결과 타입(엔티티/DTO 등) 지정
Query createNativeQuery(String sql, Class resultClass);
// 2) 결과 타입 미지정(Object / Object[])
Query createNativeQuery(String sql);
// 3) 결과 매핑(@SqlResultSetMapping) 이름으로 지정
Query createNativeQuery(String sql, String resultSetMappingName);
엔티티 모든 컬럼을 SELECT 해야 함
public Menu nativeQueryByResultType(int code) {
String sql =
"SELECT menu_code, menu_name, menu_price, category_code, orderable_status " +
"FROM tbl_menu WHERE menu_code = ?";
return (Menu) em.createNativeQuery(sql, Menu.class)
.setParameter(1, code)
.getSingleResult();
}
public List<Object[]> nativeQueryByNoResultType() {
String sql = "SELECT menu_name, menu_price FROM tbl_menu";
return em.createNativeQuery(sql).getResultList(); // List<Object[]>
}
List<Object[]> (down-cast로 직접 꺼내 사용)엔티티 + 추가 스칼라 컬럼(예: 카테고리별 메뉴 수)
@SqlResultSetMappings({
@SqlResultSetMapping(
name = "categoryCountAutoMapping",
entities = { @EntityResult(entityClass = Category.class) }, // 엔티티 자동 매핑(@Column 기반)
columns = { @ColumnResult(name = "menu_count") } // 추가 스칼라 컬럼
)
})
public class Category { ... }
public List<Object[]> nativeQueryByAutoMapping() {
String sql =
"SELECT a.category_code, a.category_name, a.ref_category_code, " +
" COALESCE(v.menu_count,0) AS menu_count " +
"FROM tbl_category a " +
"LEFT JOIN (SELECT COUNT(*) 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";
return em.createNativeQuery(sql, "categoryCountAutoMapping").getResultList();
}
Object[]에 [Category 엔티티, menu_count] 형태로 들어옴엔티티 필드명 ↔ 컬럼명 수동 연결(별칭/네이밍 불일치 시 유용)
@SqlResultSetMappings({
@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 { ... }
public List<Object[]> nativeQueryByManualMapping() {
String sql = "...(위와 동일한 SQL)...";
return em.createNativeQuery(sql, "categoryCountManualMapping").getResultList();
}
정적 SQL을 이름으로 등록해 재사용. 기동 시 파싱되어 안정적이며, 매핑도 함께 연결.
@SqlResultSetMapping(
name = "categoryCountAutoMapping2",
entities = { @EntityResult(entityClass = Category.class) },
columns = { @ColumnResult(name="menu_count") }
)
@NamedNativeQueries({
@NamedNativeQuery(
name = "Category.menuCountOfCategory",
query = "SELECT a.category_code, a.category_name, a.ref_category_code, " +
" COALESCE(v.menu_count,0) AS menu_count " +
"FROM tbl_category a " +
"LEFT JOIN (SELECT COUNT(*) 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",
resultSetMapping = "categoryCountAutoMapping2"
)
})
public class Category { ... }
public List<Object[]> selectByNamedNativeQuery() {
return em.createNamedQuery("Category.menuCountOfCategory").getResultList();
}
엔티티 매핑 반환 시:
Object[], 혹은 @SqlResultSetMapping(+@ConstructorResult로 DTO 매핑) 사용파라미터 바인딩: setParameter(1, val)(위치) 또는 setParameter("name", val)(이름) 모두 가능
페이징: setFirstResult()/setMaxResults() 지원(방언에 따라 LIMIT/OFFSET 적용)
컬럼 별칭: 매핑 시 별칭과 필드/매핑명 일치 필수(특히 자동 매핑)
성능: 정말 Native가 필요한 지 먼저 판단 → JPQL/페치조인/쿼리DSL로 대체 가능한지 검토
이식성: 특정 DB 함수/힌트 사용 시 방언 의존 고려