Native Query는 JPA에서 제공하는 기능 중 하나로, JPQL 대신 SQL 쿼리를 직접 작성해서 사용할 수 있는 방법입니다. Native Query는 데이터베이스에 종속적이며, 특정 데이터베이스에서만 동작할 수 있는 SQL 구문을 사용할 수 있습니다.
JPA의 추상화된 JPQL을 사용하는 대신, 더 강력하고 세밀한 제어가 필요한 경우 Native Query를 사용합니다.
Native Query의 특징
- SQL 기반
- JPQL과 달리, 데이터베이스 테이블 및 컬럼 이름을 직접 사용하여 쿼리를 작성합니다.
- 데이터베이스 종속성
- 특정 데이터베이스의 고유 기능(예: MySQL의 LIMIT, PostgreSQL의 JSONB 등)을 사용할 수 있습니다. 따라서 데이터베이스 변경 시 쿼리도 수정이 필요할 수 있습니다.
- 복잡한 쿼리 처리
- JPQL로 처리하기 어려운 복잡한 쿼리, 다중 조인, 서브쿼리, 또는 데이터베이스 특화 기능을 사용할 때 유용합니다.
- 결과 매핑
- Native Query의 결과는 JPA 엔티티에 매핑하거나, DTO(Data Transfer Object), 또는 맵 형식으로 받을 수 있습니다.
- 퍼포먼스 고려
- Native Query는 엔티티 매핑과는 별개로 SQL로 직접 작업하기 때문에 성능 최적화가 가능합니다.
Native Query 사용 방법
1. 기본 사용
@Query 어노테이션에 nativeQuery = true를 설정하면 Native Query를 사용할 수 있습니다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE age > :age", nativeQuery = true)
List<User> findUsersOlderThan(@Param("age") int age);
}
2. DTO로 결과 매핑
Native Query의 결과를 특정 DTO로 받을 수 있습니다.
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT u.id AS id, u.name AS name, u.email AS email FROM users u WHERE u.age > :age", nativeQuery = true)
List<UserDTO> findUserDTOs(@Param("age") int age);
interface UserDTO {
Long getId();
String getName();
String getEmail();
}
}
3. 엔티티로 결과 매핑
Native Query로 반환된 데이터를 엔티티에 매핑할 수도 있습니다.
@Query(value = "SELECT * FROM users WHERE age > :age", nativeQuery = true)
List<User> findUsersByAge(@Param("age") int age);
4. ResultSet으로 매핑
결과를 특정 클래스로 매핑하지 않고, List<Object[]> 또는 Map 형태로 받을 수도 있습니다.
@Query(value = "SELECT name, email FROM users WHERE age > :age", nativeQuery = true)
List<Object[]> findNameAndEmailByAge(@Param("age") int age);
Native Query와 JPQL의 차이
특징 | JPQL | Native Query |
쿼리 언어 | 객체 중심의 쿼리 언어 (엔티티와 필드를 대상으로 함) | SQL 언어 (테이블과 컬럼을 대상으로 함) |
데이터베이스 독립성 | 데이터베이스 독립적 | 데이터베이스 종속적 |
사용 목적 | 기본적인 CRUD 및 엔티티 간 연관 관계 처리 | 복잡한 쿼리나 특정 데이터베이스 특화 기능 처리 |
결과 매핑 | 엔티티 매핑이 기본 | 엔티티, DTO, 또는 객체 배열로 매핑 가능 |
Native Query의 장점
- 복잡한 쿼리 처리
- JPQL로 처리하기 어려운 복잡한 서브쿼리, 집계 함수, 윈도우 함수 등을 사용할 수 있습니다.
- 데이터베이스 특화 기능 활용
- 예를 들어, PostgreSQL의 JSONB, MySQL의 FULLTEXT 검색, Oracle의 CONNECT BY 등을 활용 가능.
- 성능 최적화
- 데이터베이스에 최적화된 쿼리를 작성하여 퍼포먼스를 극대화할 수 있습니다.
- 직접 제어 가능
- 엔티티 매핑이 필요 없는 경우에도 데이터를 효율적으로 조회할 수 있습니다.
Native Query의 단점
- 데이터베이스 종속성
- 데이터베이스에 특화된 SQL을 사용할 경우, 다른 데이터베이스로 이식하기 어렵습니다.
- 유지보수 어려움
- SQL 쿼리가 코드 내부에 하드코딩되므로, 변경 시 유지보수가 어렵고, 추상화 수준이 낮아집니다.
- 엔티티 매핑 한계
- 쿼리 결과가 JPA 엔티티와 잘 매핑되지 않을 경우, 추가적인 코드 작업이 필요합니다.
- 가독성 저하
- 복잡한 Native Query는 코드의 가독성을 해칠 수 있습니다.
Native Query 예제
1. 복잡한 JOIN과 서브쿼리
@Query(value = "SELECT u.name, COUNT(o.id) AS order_count " +
"FROM users u " +
"JOIN orders o ON u.id = o.user_id " +
"WHERE u.age > :age " +
"GROUP BY u.name " +
"HAVING COUNT(o.id) > 2", nativeQuery = true)
List<Object[]> findUserWithOrderCount(@Param("age") int age);
2. 데이터베이스 특화 기능 활용 (MySQL 예제)
@Query(value = "SELECT * FROM users WHERE MATCH(name, email) AGAINST(:keyword IN NATURAL LANGUAGE MODE)", nativeQuery = true)
List<User> searchUsersByFullText(@Param("keyword") String keyword);
3. DTO 매핑 (사용자 정의 결과 매핑)
@Query(value = "SELECT u.id, u.name, COUNT(o.id) AS order_count " +
"FROM users u " +
"LEFT JOIN orders o ON u.id = o.user_id " +
"GROUP BY u.id, u.name", nativeQuery = true)
List<UserOrderDTO> findUserOrderStats();
Native Query를 사용할 때 주의할 점
- SQL Injection 방지
- @Param과 같은 매개변수 바인딩을 반드시 사용하여 직접 문자열로 쿼리를 조합하지 말아야 합니다.
- 데이터베이스 변경 가능성 고려
- 데이터베이스가 변경될 가능성이 있는 프로젝트에서는 Native Query 사용을 최소화하는 것이 좋습니다.
- 테스트와 검증
- Native Query는 데이터베이스에 직접 의존하므로, 실행 전에 철저히 테스트해야 합니다.
- 복잡성 최소화
- 유지보수를 위해 쿼리를 간단하게 작성하려고 노력하고, 필요하면 JPQL로 대체합니다.
Native Query는 복잡한 요구 사항이나 성능 최적화가 필요한 경우 강력한 도구가 될 수 있습니다. 하지만 JPQL을 먼저 시도하고, 그것으로 충분하지 않을 때 Native Query를 사용하는 것이 좋습니다
'Spring > Spring JPA' 카테고리의 다른 글
@Embedded, @Embeddable 간단하게 (0) | 2025.01.07 |
---|---|
@Converter 간단하게 (1) | 2025.01.07 |
JPQL 간단히 (0) | 2025.01.06 |
FetchType 간단히 (0) | 2024.12.27 |
Cascade(영속성 전이) (0) | 2024.12.27 |