소소한개발팁
article thumbnail
Published 2023. 9. 12. 18:18
JPA - JPQL 사용 방법 컴퓨터 언어/Java
반응형

JPQL 

JPQL(Java Persistence Query Language)은 객체 지향 쿼리 언어로, JPA와 함께 사용하여 데이터베이스에서 데이터를 검색, 필터링 및 조작하는 데 사용됩니다.

 

경로 표현식

단일 속성 경로 (Single-Attribute Path): 단일 엔티티 속성에 대한 경로입니다. 예를 들어, "employee.name"은 Employee 엔티티의 "name" 속성에 대한 경로를 나타냅니다. 

 

다중 속성 경로 (Multiple-Attribute Path): 여러 개의 속성을 연결하여 엔티티 그래프 내에서 속성에 대한 경로를 나타낼 수 있습니다. 예를 들어, "department.manager.name"은 Department 엔티티의 "manager" 속성을 통해 Employee 엔티티의 "name" 속성에 대한 경로를 나타냅니다.

 

컬렉션 속성 경로 (Collection-Valued Path): 컬렉션 속성을 다룰 때 사용됩니다. 예를 들어, "orders.product"는 주문(Order) 엔티티의 "product" 속성을 통해 Product 엔티티의 속성에 대한 경로를 나타냅니다. 이 경우, 여러 개의 Product가 컬렉션으로 연결될 수 있습니다.

 

JPQL 쿼리 작성

JPQL 쿼리는 엔터티 클래스를 대상으로 작성됩니다. SQL과 유사한 구문을 사용하지만 엔터티 클래스 및 엔터티 필드를 참조합니다. 예를 들어, 모든 고객을 검색하는 JPQL 쿼리는 다음과 같이 작성할 수 있습니다.

 

SELECT alias FROM EntityName alias WHERE conditions
  • SELECT 조회 대상을 지정하는 키워드입니다.
  • alias: 엔터티를 나타내는 별칭(alias)입니다. 일반적으로 엔터티의 첫 글자를 사용하며, 이를 통해 쿼리 결과를 참조할 때 사용합니다. 
  • EntityName: 조회 대상 엔터티의 클래스 이름입니다.
  • WHERE: 선택적으로 사용되며, 필터링 조건을 지정합니다.

조회 예제

//모든 엔터티 검색
SELECT e FROM Employee e

//특정 필드만 선택.
SELECT e.name FROM Employee e

//조건을 사용한 검색
SELECT e FROM Employee e WHERE e.department = :dept
//  "Employee" 엔터티 중 부서가 특정 값(:dept)과 일치하는 엔터티를 검색합니다. 
//  이때 setParameter 메서드를 사용하여 매개변수 값을 설정할 수 있습니다.

//여러 엔터티에서 조인을 사용한 검색
SELECT p FROM Project p JOIN p.employees e WHERE e.name = :empName

//집계 함수 사용
SELECT AVG(e.salary) FROM Employee e WHERE e.department = :dept

//DTO 사용
SELECT NEW com.example.dto.EmployeeDTO(e.name, e.salary) FROM Employee e

 

페이징

setFirstResult 및 setMaxResults 메서드를 사용하여 쿼리의 시작 위치와 최대 결과 수를 설정하는 것입니다. 이를 통해 원하는 범위의 결과를 가져올 수 있습니다. 아래는 JPQL에서 페이징을 구현하는 예제입니다:

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.List;

// EntityManager를 얻어온다.
EntityManager entityManager = ...

// 페이징할 페이지 번호와 페이지당 결과 수를 정의한다.
int pageNumber = 1; // 가져올 페이지 번호
int pageSize = 10; // 페이지당 결과 수

// JPQL 쿼리를 작성한다.
String jpql = "SELECT e FROM Employee e ORDER BY e.id";

// JPQL 쿼리를 생성한다.
Query query = entityManager.createQuery(jpql);

// 페이징을 적용한다.
int startPosition = (pageNumber - 1) * pageSize;
query.setFirstResult(startPosition); // 시작 위치 설정
query.setMaxResults(pageSize); // 페이지당 결과 수 설정

// 쿼리를 실행하고 결과를 가져온다.
List<Employee> employees = query.getResultList();

주의할 점은 JPQL에서 페이징을 수행하면 결과가 데이터베이스에서 모두 검색되어 메모리에 로드되기 전까지 데이터베이스에서 필요한 범위의 데이터만 가져온다는 점입니다.

 

조인

내부 조인(Inner Join): 내부 조인은 일치하는 행만 반환합니다. 

예제: 주문(Order)과 고객(Customer) 엔터티 간의 내부 조인

SELECT o FROM Order o JOIN o.customer c WHERE c.name = 'John'

 

외부 조인(Outer Join): 외부 조인은 일치하지 않는 행도 반환할 수 있습니다. JPQL에서는 LEFT JOIN, RIGHT JOIN, 또는 FULL JOIN을 사용하여 표현할 수 있습니다. 

예제: 모든 고객(Customer)과 그들과 연관된 주문(Order)을 외부 조인으로 검색

SELECT c, o FROM Customer c LEFT JOIN c.orders o세타 조인(Theta Join): 연관 관계가 없는 엔터티 간의 조인 또는 조인 조건을 명시적으로 지정할 때 사용됩니다. 

 

예제: 부서(Department) 엔터티와 직원(Employee) 엔터티를 조인하고, 부서 ID와 직원의 관리자 ID를 비교하는 세타 조인

SELECT d, e FROM Department d, Employee e WHERE d.id = e.managerId

 

자체 조인(Self Join): 동일한 엔터티에 대한 조인으로 사용됩니다. 

예제: 자신과 관련된 직원(Employee)을 찾는 자체 조인

SELECT e1, e2 FROM Employee e1, Employee e2 WHERE e1.managerId = e2.id

 

Fetch Join: 페치 조인은 일반 조인과 다르며, 일반 조인은 데이터베이스의 테이블을 연결하고 필요한 데이터를 로드합니다. 반면, 페치 조인은 엔티티 간의 연관 관계를 따라가서 연관된 엔티티 또는 컬렉션을 함께 로드합니다. 이로 인해 추가적인 데이터베이스 쿼리가 발생하지 않고 데이터를 효율적으로 가져올 수 있습니다.

 

페치 조인의 특징: 

  • 즉시 로딩(Immediate Loading): 페치 조인을 사용하면 연관된 엔티티 또는 컬렉션을 즉시 로딩하여 추가 쿼리 없이 함께 가져올 수 있습니다.
  •  N+1 쿼리 문제 해결: 페치 조인을 사용하면 N+1 쿼리 문제를 해결할 수 있습니다. 예를 들어, 하나의 부서와 그 부서에 속한 직원들을 검색할 때 N+1 쿼리 문제가 발생하지 않도록 할 수 있습니다. 
 

JPA - N+1 문제란?

N+1 문제란 ? N+1 쿼리 문제(N+1 Query Problem)는 객체 관계 매핑(Object-Relational Mapping, ORM) 라이브러리와 관련된 성능 이슈 중 하나로, 데이터베이스와의 효율적인 데이터 검색 및 로딩을 방해하는 문제

devlopjeong12.tistory.com

페치 조인 예제: 아래의 예제 코드는 Department 엔티티와 Employee 엔티티 간의 관계에서 페치 조인을 사용하는 예제입니다.

 

// 페치 조인을 사용하여 부서와 그 부서에 속한 직원을 함께 로드하는 JPQL 쿼리
String jpqlQuery = "SELECT d FROM Department d JOIN FETCH d.employees WHERE d.name = :deptName";
TypedQuery<Department> query = entityManager.createQuery(jpqlQuery, Department.class);
query.setParameter("deptName", "HR");

Department hrDepartment = query.getSingleResult();
List<Employee> employeesInHR = hrDepartment.getEmployees(); // 추가 쿼리 없이 직원 로드

위의 코드에서 "JOIN FETCH" 구문을 사용하여 HR 부서와 그 부서에 속한 직원을 함께 로드합니다. 이로 인해 HR 부서와 관련된 모든 직원이 추가 쿼리 없이 로드됩니다. 페치 조인은 성능 최적화를 위한 중요한 기능으로, 연관된 엔티티 또는 컬렉션을 효율적으로 가져올 때 사용됩니다. 그러나 사용 시 주의해야 할 점은 엔티티 그래프를 지나치게 깊게 로드하면 성능 문제가 발생할 수 있으므로 적절하게 사용해야 합니다.

 

종속 조인(Correlated Join): 서브 쿼리에서 외부 쿼리의 결과에 의존하는 조인입니다.

예제: 서브 쿼리를 사용하여 주문(Order) 중에서 특정 고객이 만든 주문을 찾는 종속 조인

SELECT o FROM Order o WHERE EXISTS (SELECT 1 FROM Customer c WHERE c.id = o.customer.id AND c.name = 'John')

 

다형성 쿼리와 네임드 쿼리

다형성 쿼리 : JPA에서 다형성 쿼리는 상속 관계에 있는 엔티티 클래스들을 다룰 때 사용됩니다. JPA는 엔티티 상속 구조에서 하위 클래스들을 함께 검색하고 필터링하는 기능을 제공합니다. 

 

다형성 쿼리의 주요 특징과 예제: 

  • 다형성 쿼리 타입: SELECT 문에서 TYPE 연산자를 사용하여 특정 하위 클래스 유형을 검색할 수 있습니다.
  • 다형성 쿼리 다운캐스팅: 엔티티를 다운캐스팅하여 특정 하위 클래스의 속성에 접근할 수 있습니다.
SELECT e FROM Employee e WHERE TYPE(e) = FullTimeEmployee

SELECT e FROM Employee e WHERE TYPE(e) = PartTimeEmployee AND e.hourlyWage > 15.0

 

네임드 쿼리 : 네임드 쿼리는 정적으로 정의된 JPA 쿼리를 관리하고 재사용하기 위한 방법입니다. 네임드 쿼리는 애플리케이션 코드에서 문자열로 직접 쿼리를 작성하는 것보다 유지보수성이 높고, 오타나 구문 오류를 줄일 수 있습니다.

 

네임드 쿼리의 주요 특징과 예제:

  • @NamedQuery 어노테이션: 엔티티 클래스에 @NamedQuery 어노테이션을 사용하여 네임드 쿼리를 정의합니다.
  • EntityManager에서 실행: 네임드 쿼리는 EntityManager를 사용하여 실행됩니다.
  • 파라미터 바인딩: 네임드 쿼리에 파라미터를 바인딩하여 동적으로 값을 설정할 수 있습니다.
@Entity
@NamedQuery(name = "findAllEmployees", query = "SELECT e FROM Employee e")
public class Employee {
    // 엔티티 속성과 메서드
}

TypedQuery<Employee> query = entityManager.createNamedQuery("findAllEmployees", Employee.class);
List<Employee> employees = query.getResultList();

TypedQuery<Employee> query = entityManager.createNamedQuery("findEmployeeByName", Employee.class);
query.setParameter("name", "John Doe");
Employee employee = query.getSingleResult();

 

반응형

 

서브쿼리

스칼라 서브쿼리(Scalar Subquery): 스칼라 서브쿼리는 하나의 값을 반환합니다. 주로 비교 연산자와 함께 사용되어 주 쿼리의 조건을 비교하는 데 사용됩니다. 

SELECT c.name, (SELECT COUNT(o) FROM Order o WHERE o.customer = c)
FROM Customer c

 

인라인 뷰 서브쿼리(Inline View Subquery): 인라인 뷰 서브쿼리는 서브쿼리 결과를 가상의 테이블로 취급하여 주 쿼리에서 사용합니다.

SELECT c.name
FROM Customer c
WHERE c.id = (
    SELECT o.customer.id
    FROM Order o
    WHERE o.amount = (SELECT MAX(o2.amount) FROM Order o2)
)

 

JPQL의 FROM 절에서 직접 서브쿼리를 사용하는 것은 허용되지 않습니다. FROM 절은 주로 엔터티 클래스와 연관된 테이블을 지정하는 데 사용되며, 서브쿼리는 주로 WHERE 절이나 SELECT 절에서 사용됩니다.

 

벌크연산

대량의 엔티티 데이터를 한 번에 수정 또는 삭제하는 작업을 의미합니다. 벌크 연산은 엔티티를 하나씩 로드하고 수정 또는 삭제하는 것보다 효율적이며, 대량의 데이터를 일괄적으로 처리할 때 유용합니다.

 

벌크 업데이트 (Bulk Update): 

 

JPQL을 사용한 벌크 업데이트: JPQL(Query Language)을 사용하여 엔티티의 상태를 대량으로 수정할 수 있습니다.

String jpql = "UPDATE Employee e SET e.salary = e.salary * 1.1 WHERE e.department = :dept";
Query query = entityManager.createQuery(jpql);
query.setParameter("dept", department);
int updatedCount = query.executeUpdate();

 

Criteria API를 사용한 벌크 업데이트: Criteria API를 사용하여 벌크 업데이트를 수행할 수도 있습니다.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<Employee> update = cb.createCriteriaUpdate(Employee.class);
Root<Employee> root = update.from(Employee.class);
update.set(root.get("salary"), cb.prod(root.get("salary"), 1.1));
update.where(cb.equal(root.get("department"), department));
int updatedCount = entityManager.createQuery(update).executeUpdate();

 

벌크 삭제 (Bulk Delete): 

 

JPQL을 사용한 벌크 삭제: JPQL을 사용하여 엔티티를 대량으로 삭제할 수 있습니다.

String jpql = "DELETE FROM Employee e WHERE e.department = :dept";
Query query = entityManager.createQuery(jpql);
query.setParameter("dept", department);
int deletedCount = query.executeUpdate();

 

Criteria API를 사용한 벌크 삭제: Criteria API를 사용하여 벌크 삭제를 수행할 수도 있습니다.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaDelete<Employee> delete = cb.createCriteriaDelete(Employee.class);
Root<Employee> root = delete.from(Employee.class);
delete.where(cb.equal(root.get("department"), department));
int deletedCount = entityManager.createQuery(delete).executeUpdate();

 

주의할 점: 벌크 연산은 데이터베이스에 바로 쿼리를 보내므로 영속성 컨텍스트에 있는 엔티티와 동기화되지 않을 수 있습니다. 따라서 벌크 연산 후에는 영속성 컨텍스트를 재조회하거나 플러시(flush)하여 캐시를 갱신해야 합니다. 벌크 연산은 주의해서 사용해야 합니다. 실수로 대량의 데이터를 수정 또는 삭제할 수 있으므로 데이터 무결성을 위해 조심해야 합니다. 벌크 연산은 트랜잭션 내에서 수행되어야 하며, 벌크 연산이 롤백될 수 있도록 트랜잭션을 관리하는 것이 중요합니다.

 

JPQL 타입 표현

JPQL에서 기본 데이터 타입을 사용 : 엔터티 필드 이름을 사용하면 됩니다. 기본 데이터 타입은 엔터티 필드의 이름을 사용하여 쿼리에서 참조됩니다.

SELECT e.name FROM Employee e

 

파라미터와 바인딩: JPQL에서 파라미터를 사용하여 동적인 쿼리를 작성할 수 있습니다. setParameter 메서드를 사용하여 파라미터 값을 바인딩합니다.

String name = "John";
String jpql = "SELECT e FROM Employee e WHERE e.name = :name";
Query query = entityManager.createQuery(jpql);
query.setParameter("name", name);

 

조건식(Case Expression): JPQL에서 조건식은 SQL의 CASE 표현식과 유사하게 동작합니다. 데이터를 조건에 따라 다르게 선택하거나 가공할 때 사용됩니다.

SELECT CASE WHEN e.salary > 50000 THEN 'High' ELSE 'Low' END FROM Employee e

 

JPQL 함수

JPQL 함수와 연산자: JPQL은 다양한 내장 함수와 연산자를 제공합니다. 이러한 함수와 연산자를 사용하여 데이터를 가공하고 조건을 만족시킬 수 있습니다. 예를 들어, 문자열 함수, 수학 함수, 비교 연산자 등을 사용할 수 있습니다.

String jpql = "SELECT CONCAT(e.firstName, ' ', e.lastName) FROM Employee e WHERE e.salary > 50000";

 

집합 함수: 집합 함수는 데이터를 그룹화하고 통계 정보를 계산하는 데 사용됩니다. SUM, AVG, COUNT, MIN, MAX 등이 포함됩니다.

SELECT AVG(e.salary) FROM Employee e WHERE e.department = :dept

 

SQL 함수  : JPQL 쿼리에서 네이티브 SQL 함수를 직접 사용할 수 있습니다. 이렇게 하려면 FUNCTION 키워드를 사용하여 데이터베이스에 정의된 함수를 호출합니다. 하지만 이 방법은 DBMS에 종속적일 수 있습니다. 예제: PostgreSQL에서 CONCAT 함수를 사용하는 경우

SELECT e FROM Employee e WHERE FUNCTION('CONCAT', e.firstName, ' ', e.lastName) = 'John Doe'

 

애플리케이션 레벨 함수: 일부 ORM 프레임워크 및 데이터베이스 연동 라이브러리는 애플리케이션 레벨에서 사용자 정의 함수를 정의하고 사용할 수 있는 확장성을 제공합니다. 예를 들어, Hibernate의 사용자 정의 SQL 함수 등을 활용할 수 있습니다.

 

  • 사용자 정의 SQL 함수 작성: 사용자 정의 SQL 함수는 데이터베이스 내에서 정의되어야 합니다. 이 함수는 데이터베이스 벤더별로 다를 수 있으며, 주로 스토어드 프로시저 또는 사용자 정의 함수로 구현됩니다. 예를 들어, PostgreSQL에서 사용자 정의 함수를 정의하는 방법은 다음과 같습니다.
CREATE OR REPLACE FUNCTION my_custom_function(arg1 INT, arg2 INT) RETURNS INT AS $$
BEGIN
    -- Your custom logic here
    RETURN arg1 + arg2;
END;
$$ LANGUAGE plpgsql;

 

  • Hibernate에서 사용자 정의 함수 등록: Hibernate에서 사용자 정의 SQL 함수를 사용하려면 Hibernate Dialect 클래스를 확장하고 registerFunction 메서드를 사용하여 함수를 등록해야 합니다. 함수 등록은 Hibernate의 데이터베이스 방언에 종속적입니다.
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;

public class CustomPostgreSQLDialect extends PostgreSQLDialect {
    public CustomPostgreSQLDialect() {
        super();
        registerFunction("my_custom_function", new StandardSQLFunction("my_custom_function", StandardBasicTypes.INTEGER));
    }
}

 

  • Hibernate 쿼리에서 사용자 정의 함수 사용: 이제 Hibernate 쿼리에서 사용자 정의 함수를 사용할 수 있습니다. HQL 또는 Criteria API에서 함수를 호출하여 쿼리에서 활용할 수 있습니다. Hibernate가 사용자 정의 SQL 함수를 데이터베이스에 매핑하면 쿼리 실행 시 해당 함수가 호출됩니다.
String hql = "SELECT e FROM Employee e WHERE my_custom_function(e.salary, :bonus) > 1000";
Query query = session.createQuery(hql);
query.setParameter("bonus", 500);

List<Employee> employees = query.list();

 

 

반응형

'컴퓨터 언어 > Java' 카테고리의 다른 글

JPA - OSIV 패턴  (0) 2023.09.23
JPA - N+1 문제란?  (0) 2023.09.12
JPA - 값 타입 사용 방법  (0) 2023.09.11
JPA - Proxy(프록시) 이해하기  (0) 2023.09.07
JPA - 상속 관계 및 MappedSuperclass  (0) 2023.09.07
profile

소소한개발팁

@개발자 뱅

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!