Spring 4

CJI0524·2025년 2월 20일

Spring Boot

목록 보기
21/21

1. 네이티브 쿼리 활용

특정 데이터베이스 기능이나 구문을 직접 사용해야 할 필요가 있는데 이러한 경우, Spring Data JPA에서는 네이티브 SQL 쿼리를 작성하고 실행할수 있는 기능을 지원한다.

1.1. 네이티브 쿼리의 필요성

네이티브 쿼리의 필요성은 다음과 같다.

1. 특정 데이터베이스 기능 사용

  • 네이티브 쿼리를 쓰면 오라클, MySQL 등 각 DBMS가 제공하는 고유한 함수나 옵티마이저 힌트, 파티셔닝 등 특화된 기능들을 그대로 사용 가능. 이런 기능들은 JPQL에서는 지원하지 않는 경우가 많아 성능 최적화나 특정 기능이 필요할 때는 네이티브 쿼리가 필수적

2. 복잡한 쿼리 실행

  • 복잡한 조인, 서브쿼리 같은 SQL 기능을 활용해야 할 경우, JPQL로 표현하기 어려울 수 있음 이럴 땐 SQL의 모든 기능을 쓸 수 있는 네이티브 쿼리가 훨씬 유연하고 강력함.

3. 기존 SQL 쿼리 통합

  • 이미 구축된 시스템이나 레거시 코드에서 수많은 SQL 쿼리를 사용하고 있을 때, 네이티브 쿼리를 사용하면 기존에 검증된 SQL 쿼리를 그대로 활용할 수 있어서 시스템 통합이나 마이그레이션 작업이 수월해지고, 테스트 및 디버깅 비용도 절감할 수 있음

1.2. 네이티브 쿼리 사용 방법

@Query 어노테이션내에서 nativeQuery 속성을 true로 설정하고, SQL 쿼리를 직접 작성

사용 예제

✍️ 작성

package org.example.springdatajpa5;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface UserRepository extends JpaRepository<User, Long> {

    // 네이티브 SQL을 사용한 사용자 조회 (이메일에 특정 문자열 포함)
    @Query(value = "SELECT * FROM jpa_user WHERE email LIKE %?1%", nativeQuery = true)
    List<User> findByEmailNative(String email);

    // 나이가 30 초과이고 상태가 'ACTIVE'인 사용자 수 조회
    @Query(value = "SELECT COUNT(*) FROM jpa_user WHERE age > 30 AND status = 'ACTIVE'", nativeQuery = true)
    int countActiveUsersOlderThan30();

    // 네이티브 쿼리를 사용하여 이름에 특정 문자열이 포함된 사용자들의 name, email 조회
    // 결과는 Object[] 배열의 List로 반환됨
    @Query(value = "SELECT name, email FROM jpa_user WHERE name LIKE %:name%", nativeQuery = true)
    List<Object[]> findUsersByNameNative(@Param("name") String name);
}

2. Criteria API

Criteria API는 JPA의 한 부분으로, SQL이나 JPQL 문자열을 직접 작성하지 않고도 자바 코드로 쿼리를 만드는 방법이다. 즉, 코드 내에서 타입에 안전한 방식으로 쿼리를 구성할 수 있어서, 쿼리를 작성할 때 실수를 줄이고, 조건에 따라 동적으로 쿼리를 생성할 수 있게 해준다. 개발자가 마치 SQL을 다루듯이 자바 코드로 쿼리 연산을 수행할 수 있게 해주는 객체지향 API라고 보면 된다.

2.1. Criteria API의 구성 요소

CriteriaBuilder

  • 쿼리를 생성하고 수정하는 데 사용되는 팩토리 클래스
  • 이 객체는 쿼리의 각 조건과 표현을 생성
  • 즉 쿼리 제작의 기본 도구라고 할 수 있음

CriteriaQuery

  • 생성할 쿼리를 정의
  • 반환할 엔티티 타입과 WHERE, GROUPBY, ORDER BY 같은 쿼리 조건을 지정
  • 즉 쿼리의 설계도라고 할 수 있음

Root

  • 쿼리의 FROM 절에 해당하는 주 엔티티를 지정
  • Root 객체를 사용하여 쿼리의 기준이 되는 엔티티를 정의
  • 쿼리의 시작점이자 기준이 되는 엔티티

2.2. Criteria API를 이용한 동적 쿼리 생성

Criteria API로 동적 쿼리를 생성하는 기본 절차를 아래와 같이 정리할 수 있음

1. CriteriaBuilder 인스턴스 생성

  • EntityManager로부터 CriteriaBuilder 객체를 얻어옴
    이 객체는 쿼리의 조건 및 구조를 정의하는 데 사용

2. CriteriaQuery 객체 생성

  • CriteriaBuilder를 사용해서 반환될 결과 타입(예: User)을 지정하는 CriteriaQuery 객체를 생성

3. Root 정의

  • 쿼리의 FROM 절에 해당하는 Root 객체를 생성하여, 메인 테이블(엔티티)를 지정

4. 조건 추가 (Where 절)

  • CriteriaBuilder를 통해 필요한 검색 조건(Predicate)을 생성하고, 이를 CriteriaQuery의 where 절에 추가

5. 쿼리 실행

  • 완성된 CriteriaQueryEntityManager를 사용해 실행하고 결과를 반환

2.3. Criteria API 사용 예제

✍️ 작성

package com.example.springdatajpa3;

import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
    // 엔티티 매니저를 통해 DB와의 연결을 관리함.
    private final EntityManager entityManager;

    @Override
    public List<User> findUsersByName(String name) {
        // CriteriaBuilder: Criteria 쿼리를 만들기 위한 팩토리 객체. 다양한 조건과 표현식을 생성할 수 있음.
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

        // CriteriaQuery: 반환할 결과 타입(User)을 지정하며, 쿼리 구조(SELECT, WHERE 등)를 정의함.
        CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);

        // Root: 쿼리의 FROM 절에 해당하는 부분. 여기서는 User 엔티티가 기준이 됨.
        Root<User> user = query.from(User.class);

        // SELECT 절에서 user를 선택하고, WHERE 조건으로 name 필드가 포함된 값을 찾도록 지정.
        // criteriaBuilder.like()를 사용해서 SQL의 LIKE 연산자와 동일한 효과를 냄.
        query.select(user)
             .where(criteriaBuilder.like(user.get("name"), "%" + name + "%"));

        // 완성된 CriteriaQuery를 기반으로 실제 쿼리를 실행해 결과를 가져옴.
        return entityManager.createQuery(query).getResultList();
    }

    @Override
    public List<User> findusersDynamically(String name, String email) {
        // CriteriaBuilder를 사용하여 쿼리 생성 준비.
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> user = query.from(User.class);

        // 동적으로 WHERE 조건을 추가하기 위해 Predicate 리스트를 생성.
        List<Predicate> predicates = new ArrayList<>();

        // name 파라미터가 null이 아니라면, name 조건을 추가.
        if(name != null) {
            predicates.add(cb.equal(user.get("name"), name));
        }
        // email 파라미터가 null이 아니라면, email 조건을 추가.
        if(email != null) {
            predicates.add(cb.equal(user.get("email"), email));
        }

        // 리스트에 담긴 모든 조건들을 AND 연산자로 결합하여 WHERE 절에 설정.
        query.select(user)
             .where(cb.and(predicates.toArray(new Predicate[0])));

        // 위 주석처럼 사용자가 입력한 값에 따라 조건이 동적으로 생성됨.
        // 예시:
        // - name = null, email = null → 전체 User 조회 (WHERE 조건 없음)
        // - name != null, email = null → name 조건만 적용
        // - name = null, email != null → email 조건만 적용
        // - name != null, email != null → 두 조건 모두 적용

        // 실제 쿼리를 실행하고 결과 리스트를 반환.
        return entityManager.createQuery(query).getResultList();
    }
}

해당 코드에서는 CriteriaBuilder로 조건을 생성하고,
CriteriaQuery로 쿼리의 전체 구조를 잡으며,
Root로 기준 엔티티를 지정함.

동적으로 조건을 추가할 수 있는 예제로 필요에 따라 추가 조건을 유연하게 설정할 수 있음

profile
개발돌이

0개의 댓글