Spring/Spring Data JPA

[자바 ORM 표준 JPA 프로그래밍] 10-4. 객체지향 쿼리 언어: Native SQL

noahkim_ 2025. 4. 24. 10:39

김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.

 

1. Native SQL

  • JPA는 JPQL이 아닌 DB 고유의 SQL 문법을 직접 사용하는 방법을 지원합니다.
  • 결과 매핑 어노테이션을 사용하여 응답을 정의할 수 있습니다.

 

사용 상황

상황 이유
복잡한 조인 / 서브쿼리
JPQL로 표현하기 어려운 경우
DB 특화 기능 사용
예: 윈도우 함수, 특정 DB 함수 등
성능 최적화
JPQL보다 효율적인 쿼리 필요 시
기존 SQL 자산 재사용
DB에 이미 작성된 쿼리를 그대로 쓰고 싶을 때

 

 

예제) Native SQL

더보기
@Test
@Transactional
void native_query_entity() {
    String sql = "SELECT ID, NAME, NICKNAME, AGE, TEAM_ID FROM USER WHERE AGE > ?";
    Query nativeQuery = entityManager.createNativeQuery(sql, User.class).setParameter(1, 20);
    List<User> result = nativeQuery.getResultList();

    for (User row : result) {
        System.out.println(row);
    }
}
  • SELECT문에 누락된 컬럼이 있으면 예외 발생 ‼
  • JPA는 Native SQL 사용 시, 엔티티 매핑을 위해 컬럼 명과 엔티티 필드를 1:1 매핑하려고 합니다.

 

@Test
@Transactional
void native_query_object() {
    String sql = "SELECT ID, NAME, NICKNAME, AGE, TEAM_ID FROM USER WHERE AGE > ?";
    Query nativeQuery = entityManager.createNativeQuery(sql).setParameter(1, 20);
    List<Object[]> result = nativeQuery.getResultList();

    for (Object[] row : result) {
        System.out.println(row[0]);
        System.out.println(row[1]);
        System.out.println(row[2]);
        System.out.println(row[3]);
    }
}
  • Object로 결과 받을수도 있음

 

2. Named Native Query

  • Native Query를 사전에 정의합니다.
    • 엔티티 클래스에 @NamedNativeQuery로 정의
    • 간단한 쿼리일 경우, 직접 정의 가능
  • 컴파일 시점에 정해지며, 재사용성이 높음

 

예제

더보기
@NamedNativeQuery(
    name = "User.findAll",
    query = "SELECT * FROM springboot_document.user",
    resultClass = User.class
)
@Entity
@Getter
@Setter
@NoArgsConstructor
@Access(AccessType.FIELD)
public class User {
@Test
@Transactional
void native_query_result_class() {
    Query nativeQuery = entityManager.createNamedQuery("User.findAll");
    List<User> result = nativeQuery.getResultList();

    for (User row : result) {
        System.out.println(row);
    }
}

 

결과 매핑 어노테이션

어노테이션 적용 대상 설명
@SqlResultSetMapping 클래스 레벨 결과 매핑 정의를 모아놓는 컨테이너 역할
@EntityResult @SqlResultSetMapping 내부 엔티티 매핑을 정의 (엔티티 클래스 지정)
@FieldResult @EntityResult 내부 결과 컬럼과 엔티티 필드 간 매핑 지정
@ColumnResult @SqlResultSetMapping 내부 스칼라(단일 값) 타입 매핑
@ConstructorResult @SqlResultSetMapping 내부 DTO 생성자 기반 매핑
@NamedNativeQuery 클래스 레벨 SQL과 결과 매핑을 함께 명시 가능

 

 

예제) 결과 매핑 어노테이션

더보기
@SqlResultSetMapping(
    name = "UserEntityAndDtoMapping",
    entities = {
        @EntityResult(
            entityClass = User.class,
            fields = {
                @FieldResult(name = "id", column = "id"),
                @FieldResult(name = "name", column = "name"),
                @FieldResult(name = "nickname", column = "nickname"),
                @FieldResult(name = "age", column = "age"),
                @FieldResult(name = "team", column = "team_id")
            }
        )
    },
    columns = {
        @ColumnResult(name = "id", type = Integer.class),
        @ColumnResult(name = "name", type = String.class)
    },
    classes = {
        @ConstructorResult(
            targetClass = UserResponse.class,
            columns = {
                @ColumnResult(name = "id", type = Integer.class),
                @ColumnResult(name = "name", type = String.class)
            }
        )
    }
)
@NamedNativeQuery(
    name = "User.findUserAndDto",
    query = "SELECT id, name, nickname, age, team_id FROM springboot_document.user WHERE age > :minAge",
    resultSetMapping = "UserEntityAndDtoMapping"
)
@Entity
@Getter
@Setter
@NoArgsConstructor
@Access(AccessType.FIELD)
public class User {
@Test
@Transactional
void native_query_result_mapping() {
    Query nativeQuery = entityManager.createNamedQuery("User.findUserAndDto")
            .setParameter("minAge", 20);
    List<Object[]> result = nativeQuery.getResultList();

    for (Object[] row : result) {
        User user = (User) row[0];
        UserResponse dto = (UserResponse) row[1];

        System.out.println(user);
        System.out.println(dto);
        System.out.println();
    }
}

 

3. 스토어드 프로시저

  • @NamedStoredProcedureQuery 로 이름 지정 가능
  • 복잡한 로직을 DB에 위임 가능

 

예제

더보기
CREATE PROCEDURE proc_multiply (INOUT inParam INT, INOUT outParam INT)
BEGIN
    SET outParam = inParam * 2;
END;
@NamedStoredProcedureQuery(
    name = "multiply",
    procedureName = "proc_multiply",
    parameters = {
        @StoredProcedureParameter(name = "inParam", mode = ParameterMode.IN, type = Integer.class),
        @StoredProcedureParameter(name = "outParam", mode = ParameterMode.OUT, type = Integer.class),
    }
)
@Entity
@Getter
@Setter
@NoArgsConstructor
@Access(AccessType.FIELD)
public class User {
@Test
@Transactional
void native_query_stored_procedure_declaratively() {
    StoredProcedureQuery spq = entityManager.createNamedStoredProcedureQuery("multiply");
    spq.setParameter("inParam", 100);
    spq.execute();

    assertEquals((Integer) spq.getOutputParameterValue("outParam"), 200);
}
@Test
@Transactional
void native_query_stored_procedure_programmatibly() {
    StoredProcedureQuery spq = entityManager.createStoredProcedureQuery("proc_multiply");
    spq.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
    spq.registerStoredProcedureParameter(2, Integer.class, ParameterMode.OUT);
    spq.setParameter(1, 100);
    spq.execute();

    assertEquals((Integer) spq.getOutputParameterValue(2), 200);
}