[Spring] Native Query

배창민·2025년 10월 27일
post-thumbnail

JPA Native Query

ORM을 쓰면서도 DB 고유 SQL을 그대로 활용하고 싶을 때 쓰는 게 Native query. 복잡한 집계/힌트/특정 함수 등 JPQL로 표현 어려운 쿼리를 그대로 사용하되, 영속성 컨텍스트 연동(엔티티 매핑 시) 이점은 그대로 챙길 수 있음.


1) API 사용법 요약

// 1) 결과 타입(엔티티/DTO 등) 지정
Query createNativeQuery(String sql, Class resultClass);

// 2) 결과 타입 미지정(Object / Object[])
Query createNativeQuery(String sql);

// 3) 결과 매핑(@SqlResultSetMapping) 이름으로 지정
Query createNativeQuery(String sql, String resultSetMappingName);
  • resultClass가 엔티티일 때: SELECT 목록이 엔티티의 모든 매핑 컬럼을 포함해야 합니다. 그래야 영속 엔티티로 매핑되고 관리됩니다.
  • Object / Object[]: 스칼라 값(일부 컬럼)만 뽑을 때 사용. 비영속입니다.

2) 대표 패턴별 예시

2-1. 결과 타입 지정 (엔티티로 매핑)

엔티티 모든 컬럼을 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();
}
  • 반환: 영속 엔티티(변경 감지 O)

2-2. 결과 타입 미지정 (스칼라/부분 컬럼)

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로 직접 꺼내 사용)

2-3. 자동 결과 매핑 (@SqlResultSetMapping)

엔티티 + 추가 스칼라 컬럼(예: 카테고리별 메뉴 수)

@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] 형태로 들어옴
    (구현에 따라 엔티티와 컬럼 순서는 매핑 정의에 좌우)

2-4. 수동 결과 매핑 (@FieldResult)

엔티티 필드명 ↔ 컬럼명 수동 연결(별칭/네이밍 불일치 시 유용)

@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();
}

3) Named Native Query

정적 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();
}

4) 체크리스트

  • 엔티티 매핑 반환 시:

    • SELECT 목록이 모든 매핑 컬럼 포함 → 영속 엔티티로 관리됨(변경 감지 O)
    • 일부만 뽑을 땐 Object[], 혹은 @SqlResultSetMapping(+@ConstructorResult로 DTO 매핑) 사용
  • 파라미터 바인딩: setParameter(1, val)(위치) 또는 setParameter("name", val)(이름) 모두 가능

  • 페이징: setFirstResult()/setMaxResults() 지원(방언에 따라 LIMIT/OFFSET 적용)

  • 컬럼 별칭: 매핑 시 별칭과 필드/매핑명 일치 필수(특히 자동 매핑)

  • 성능: 정말 Native가 필요한 지 먼저 판단 → JPQL/페치조인/쿼리DSL로 대체 가능한지 검토

  • 이식성: 특정 DB 함수/힌트 사용 시 방언 의존 고려


핵심 요약

  • 간단/표준 쿼리: JPQL 우선
  • DB 특정 기능·복잡 집계: Native Query + 결과 매핑
  • 재사용 필요: @NamedNativeQuery로 정적 등록
profile
개발자 희망자

0개의 댓글