튼튼발자 개발 성장기🏋️

객체지향 쿼리: JPQL 본문

API/JPA

객체지향 쿼리: JPQL

시뻘건 튼튼발자 2024. 9. 7. 13:26
반응형

객체지향 쿼리 언어는 데이터베이스의 테이블이 아닌 객체를 대상으로 데이터를 조회하는 쿼리 방법이다. 그러므로 데이터베이스에 의존하지 않고 개발할 수 있다는 장점이 생긴다.

JPQL

JPQL(Java Persistence Query Language)은 객체 지향적으로 데이터를 조회하는 쿼리 언어로, SQL과 유사하지만 엔티티를 대상으로 쿼리를 작성한다. 가장 많이 사용되는 쿼리 방식다.

  • JPQL은 데이터베이스 테이블이 아닌 엔티티와 속성을 대상으로 쿼리한다.
String jpql = "SELECT e FROM Employee e WHERE e.name = :name";
TypedQuery<Employee> query = em.createQuery(jpql, Employee.class);
query.setParameter("name", "John");
List<Employee> employees = query.getResultList();

파라미터 바인딩

JPQL에서 파라미터 바인딩은 쿼리문 내의 값을 동적으로 설정하는 방식이다.

  • 이름 기반 파라미터 바인딩: 쿼리 내에서 :와 파라미터를 사용하여 지정한다.
  • 위치 기반 파라미터 바인딩: 쿼리 내에서 ?와 숫자를 사용하여 파라미터의 위치를 지정한다. 이 방식은 단순한 쿼리에서 사용할 수 있지만, 파라미터의 순서를 맞추는 데 주의해야 한다.
// 이름 기반 파라미터 바인딩
TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e WHERE e.name = :name", Employee.class);
query.setParameter("name", "John");
List<Employee> employees = query.getResultList();

// 위치 기반 파라미터 바인딩
String jpql = "SELECT e FROM Employee e WHERE e.name = ?1 AND e.salary > ?2";
TypedQuery<Employee> query = em.createQuery(jpql, Employee.class);
query.setParameter(1, "John");   // 첫 번째 파라미터에 "John" 설정
query.setParameter(2, 50000);    // 두 번째 파라미터에 50000 설정
List<Employee> employees = query.getResultList();

 

프로젝션

JPQL에서 SELECT 절에 조회할 대상을 지정하는 것을 의미한다. 조회할 대상은 엔티티 객체 전체, 특정 필드, 또는 둘 이상의 엔티티나 필드를 선택할 수 있다.

엔티티 프로젝션

조회한 엔티티는 영속성 컨텍스트에서 관리된다.

String jpql = "SELECT m FROM Member m";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();

임베디드 타입 프로젝션

임베디드 타입은 엔티티 타입이 아닌 값 타입이다. 그렇기 때문에 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.

String jpql = "SELECT m.address FROM Member m";
List<Address> addresses = em.createQuery(jpql, Address.class).getResultList();

스칼라 타입 프로젝션

조회한 엔티티는 영속성 컨텍스트에서 관리된다.

String jpql = "SELECT m.name, m.age FROM Member m";
List<Object[]> results = em.createQuery(jpql).getResultList();
for (Object[] result : results) {
    String name = (String) result[0];
    Integer age = (Integer) result[1];
    // ...
}

// 스칼라 타입과 엔티티를 함께 조회
String jpql = "SELECT m.name, m.team FROM Member m";
List<Object[]> result = em.createQuery(jpql).getResultList();
for (Object[] row : result) {
    String name = (String) row[0];
    Team team = (Team) row[1];
}

// 생성자 사용
String jpql = "SELECT new com.example.dto.MemberDTO(m.name, m.age) FROM Member m";
List<MemberDTO> result = em.createQuery(jpql, MemberDTO.class).getResultList();

페이징 API

JPQL은 페이징 처리를 위한 API를 제공한다. setFirstResult()와 setMaxResults() 메서드를 통해 쿼리의 결과를 원하는 범위로 제한할 수 있다.

String jpql = "SELECT m FROM Member m ORDER BY m.name DESC";
List<Member> members = em.createQuery(jpql, Member.class)
                         .setFirstResult(0)  // 시작 위치
                         .setMaxResults(10)  // 가져올 최대 개수
                         .getResultList();

집합과 정렬

JPQL은 집합 함수와 정렬을 지원한다. COUNT, SUM, AVG, MAX, MIN 등의 집합 함수를 사용할 수 있으며, ORDER BY를 통해 정렬도 가능하다.

String jpql = "SELECT COUNT(m), AVG(m.age) FROM Member m";
Object[] result = em.createQuery(jpql).getSingleResult();
Long count = (Long) result[0];
Double averageAge = (Double) result[1];

JPQL 조인

JPQL에서 조인은 객체 그래프를 탐색하는 방식으로 이루어진다. INNER JOIN, LEFT JOIN을 사용할 수 있다.

 

페치 조인

페치 조인은 JPA에서 성능 최적화를 위해 사용하는 조인의 일종으로, 연관된 엔티티나 컬렉션을 한 번의 쿼리로 함께 조회한다. 아래 예제는 지연 로딩을 방지하고 한 번의 쿼리로 Member와 Team을 함께 조회한다. 페치 조인은 N+1 문제를 해결하는 데 유용하다.

String jpql = "SELECT m FROM Member m JOIN FETCH m.team";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

내부 조인

내부 조인은 두 개 이상의 엔티티 간의 교집합을 반환한다. 즉, 두 테이블 간의 일치하는 데이터만 결과로 반환된다. JPQL에서는 INNER JOIN 키워드를 사용하여 내부 조인을 수행한다.

String jpql = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName";
List<Member> result = em.createQuery(jpql, Member.class)
                        .setParameter("teamName", "TeamA")
                        .getResultList();

외부 조인

외부 조인은 조인 조건에 맞지 않는 데이터도 결과에 포함한다. LEFT OUTER JOIN과 RIGHT OUTER JOIN이 있으며, 왼쪽 테이블 또는 오른쪽 테이블의 모든 데이터를 포함한다. JPQL에서는 LEFT JOIN을 통해 외부 조인을 수행할 수 있다. JPQL에서는 오른쪽 외부 조인을 직접 지원하지는 않지만, 왼쪽 외부 조인만 사용할 수 있다.

String jpql = "SELECT m FROM Member m LEFT JOIN m.team t WHERE t.name IS NULL";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

컬렉션 조인

컬렉션 조인은 컬렉션 타입의 필드를 조인할 때 사용한다. 컬렉션 조인을 통해 연관된 컬렉션의 데이터를 조회할 수 있다.

String jpql = "SELECT t FROM Team t JOIN t.members m WHERE m.age > :age";
List<Team> result = em.createQuery(jpql, Team.class)
                      .setParameter("age", 30)
                      .getResultList();

세타 조인

세타 조인은 조인 조건이 없는 조인이다. 즉, 두 엔티티 간의 모든 조합을 반환한다. JPQL에서 세타 조인은 조인 조건 없이 단순히 JOIN을 사용하여 수행할 수 있다.

String jpql = "SELECT m, t FROM Member m JOIN Team t";
List<Object[]> result = em.createQuery(jpql).getResultList();

경로 표현식

경로 표현식은 JPQL에서 엔티티와 연관된 객체의 필드를 탐색할 때 사용한다. 경로 표현식은 주로 연관 관계가 있는 필드에 접근할 때 사용된다. 아래 예제에서 t.members.size는 Team 엔티티의 members 컬렉션의 크기를 의미한다.

String jpql = "SELECT t FROM Team t WHERE t.members.size > 0";
List<Team> result = em.createQuery(jpql, Team.class).getResultList();

서브 쿼리

JPQL에서 서브 쿼리는 WHERE 절, HAVING 절, FROM 절에서 사용할 수 있다. 하지만 FROM 절에서 서브 쿼리를 사용하는 것은 제한적이다.

String jpql = "SELECT m FROM Member m WHERE m.age > (SELECT AVG(m2.age) FROM Member m2)";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

조건식

JPQL에서 조건식은 SQL과 비슷하게 WHERE, HAVING 절에서 사용한다. AND, OR, NOT 등의 논리 연산자와 BETWEEN, LIKE, IN 등의 조건식을 지원한다.

String jpql = "SELECT m FROM Member m WHERE m.age BETWEEN :startAge AND :endAge";
List<Member> result = em.createQuery(jpql, Member.class)
                        .setParameter("startAge", 18)
                        .setParameter("endAge", 30)
                        .getResultList();

다형성 쿼리

JPA는 상속 관계에서 다형성 쿼리를 지원한다. 부모 클래스 타입으로 쿼리를 작성하면, 상속받은 모든 자식 클래스의 엔티티가 함께 조회된다. 아래 예제 코드는 Item이라는 부모 클래스로 조회하되, Book 타입의 자식 엔티티만 조회한다. TREAT를 사용하면 부모 타입으로 조회된 결과에서 특정 자식 타입으로 캐스팅하여 쿼리를 작성할 수 있습니다. 이는 상속 관계에서 자식 타입에 특화된 속성이나 메서드를 사용할 때 유용합니다.

// TYPE
String jpql = "SELECT i FROM Item i WHERE TYPE(i) = Book";
List<Item> result = em.createQuery(jpql, Item.class).getResultList();

// TREAT
String jpql = "SELECT b FROM Item i TREAT(i AS Book) b WHERE b.author = :author";
List<Book> result = em.createQuery(jpql, Book.class)
                      .setParameter("author", "J.K. Rowling")
                      .getResultList();

사용자 정의 함수 호출

JPA 2.1부터 사용자 정의 데이터베이스 함수를 호출할 수 있다. 이를 통해 데이터베이스 벤더에 특화된 함수를 JPQL에서 사용할 수 있다.

public class MyDialect extends H2Dialect {
	public MyDialect() {
    	registerFunction("my_custom_function", new StandardSQLFunction("my_custom_function", StandardBasicTypes.STRING));
    }
}

// ...

String jpql = "SELECT FUNCTION('my_custom_function', m.name) FROM Member m";
List<String> result = em.createQuery(jpql, String.class).getResultList();

Named 쿼리: 정적 쿼리

JPQL 쿼리는 크게 동적 쿼리와 정적 쿼리로 나눌 수 있다.

동적 쿼리

실행 시점에 동적으로 생성되는 query다. query의 구성 요소가 런타임에 결정되며, 일반적으로 query 생성 시 다양한 조건에 따라 다르게 조합된다.

정적 쿼리

컴파일 시점에 정의되고, 실행 시점에는 변하지 않는 qeury다. qeury 의 내용이 고정되어 있으며, 주로 @NamedQuery를 통해 사용된다.

정적 qeury는 앱 로딩 시점에 JPQL 문법을 미리 파싱해두기 때문에 런타임 때 오류가 발생되는 동적 qeury와 반대로 오류를 빨리 확인할 수 있고 사용하는 시점에는 파싱된 결과를 재사용하므로 성능상 이슈도 있다.

@Entity
@NamedQuery(
    name = "Member.findByName",
    query = "SELECT m FROM Member m WHERE m.name = :name"
)
public class Member {
    ...
}

 

반응형

'API > JPA' 카테고리의 다른 글

스프링 데이터 JPA  (0) 2024.09.08
객체지향 쿼리 심화  (0) 2024.09.08
값 타입  (0) 2024.09.02
프록시와 연관관계 관리  (0) 2024.08.27
Deep한 JPA 매핑  (0) 2024.08.24