Hibernate HQL 쿼리 문법 정리

이 글에서는 Hibernate에서 사용하는 HQL(Hibernate Query Language)의 주요 쿼리 문법들을 정리한다.

테스트를 위해 세 개의 엔티티 클래스를 정의한다. Department와 Course는 일대다 관계, Course와 Employee는 일대다 관계이다.

Department 클래스:

public class Department
{
    private int deptId;
    private String deptName;
    private String deptType;
    private Set<Course> courses;
    ..........
}

Course 클래스:

public class Course
{
    private int courseId;
    private String courseName;
    private Department department;
    private Set<Employee> employees;
    ..........
}

Employee 클래스:

public class Employee
{
    private int empId;
    private String empName;
    private String gender;
    private Course course;
    ..........
}

1. 기본 조회 쿼리

List<Department> departments = session.createQuery("select d from Department d").list();

가장 단순한 HQL 查询문으로, 모든 Department 객체를 List 형태로 반환한다.

2. 위치 기반 파라미터 바인딩

/**
 * HQL에서 ?를 사용하여 파라미터를 바인딩할 수 있다
 * JDBC에서는 인덱스가 1부터 시작하지만, HQL에서는 0부터 시작한다
 */
List<Employee> employees = session.createQuery("select e from Employee e where empName like ?")
                            .setParameter(0, "%김%")
                            .list();

HQL에서도 JDBC와 유사하게 ? 표기법을 사용할 수 있다. 주의사항: JDBC의 setParameter 인덱스는 1부터 시작하지만, Hibernate에서는 0부터 시작한다.

3. 이름 기반 파라미터 바인딩

/**
 * HQL에서 :이름 형식으로 별칭을 사용하여 파라미터를 바인딩할 수 있다
 */
List<Employee> employees = session.createQuery("select e from Employee e where empName like :name and gender like :gender")
                            .setParameter("name", "%최%")
                            .setParameter("gender", "%남%")
                            .list();

4. 단일 결과 조회

/**
 * 결과가 하나만 반환될 경우 uniqueResult 메서드를 사용한다
 */
Long count = (Long)session.createQuery("select count(e) from Employee e where empName like :name and gender like :gender")
                    .setParameter("name", "%최%")
                    .setParameter("gender", "%남%")
                    .uniqueResult();

Employee emp = (Employee)session.createQuery("select e from Employee e where empId = ?")
                    .setParameter(0, 1)
                    .uniqueResult();

5. 프로젝션 쿼리

/**
 * 특정 컬럼만 조회하는 프로젝션 쿼리
 * 여러 값을 조회하면 Object[] 배열에 저장된다
 */
List<Object[]> results = session.createQuery("select e.empName, e.gender from Employee e where empName like :name and gender like :gender")
                        .setParameter("name", "%이%")
                        .setParameter("gender", "%남%")
                        .list();

6. 연관 객체 탐색 쿼리

/**
 * 엔티티 간의 연관 관계를 통해 다른 엔티티의 필드可以直接 접근하여 조회
 */
List<Employee> employees = session.createQuery("select e from Employee e where e.course.courseName like :courseName and gender like :gender")
                            .setParameter("courseName", "%프로그래밍%")
                            .setParameter("gender", "%여%")
                            .list();

주의사항: 연관 객체를 직접 탐색하여 조회하면 내부적으로 Cross Join(카티션 곱)이 발생하여 성능이 저하될 수 있으므로 권장하지 않는다.

7. IN 절을 활용한 목록 조회

/**
 * IN 절을 사용하여 목록 조회 가능
 * setParameterList 메서드로 리스트 형태의 파라미터를 전달한다
 * 별칭 방식과 ? 방식 혼합 시 ?가 먼저 와야 한다
 */
List<Employee> employees = session.createQuery("select e from Employee e where e.course.courseId in (:courseIds) and e.gender like :gender")
                            .setParameterList("courseIds", new Integer[]{1, 2, 3})
                            .setParameter("gender", "%여%")
                            .list();

IN 절 사용 시 별칭 방식과 ? 방식 혼합은 Hibernate 오류를 발생시킬 수 있으므로 주의해야 한다.

8. 페이징 처리

/**
 * setFirstResult와 setMaxResults로 페이징 처리 가능
 * setFirstResult: 시작 위치 (offset)
 * setMaxResults: 조회할 데이터 수 (pageSize)
 */
List<Employee> employees = session.createQuery("select e from Employee e where e.course.courseName like :courseName and gender like :gender")
                            .setParameter("courseName", "%프로그래밍%")
                            .setParameter("gender", "%여%")
                            .setFirstResult(0)
                            .setMaxResults(10)
                            .list();

9. 내부 조인 쿼리

/**
 * JOIN을 사용하여 효율적인 조인 조회 가능
 * 연관 객체 탐색보다 조인을 사용하는 것이 성능상 유리하다
 */
List<Employee> employees = session.createQuery("select e from Employee e join e.course c where c.courseId = 2")
                            .list();

HQL과 SQL의 조인 문법 차이점:

<strong>HQL</strong>: select e from Employee e join e.course c

<strong>SQL</strong>: select e.* from Employee e inner join Course c on e.course_id = c.course_id

10. 외부 조인 쿼리

/**
 * left join: 왼쪽 테이블 기준 / right join: 오른쪽 테이블 기준
 */
List<Object[]> results = session.createQuery("select c.courseName, count(e.course.department.deptId) from Employee e right join e.course c group by c.courseId")
                        .list();

11. DTO를 활용한 결과 매핑

/**
 * 여러 컬럼을 조회할 때 DTO 클래스를 사용하여 결과를 직접 매핑할 수 있다
 * DTO 클래스에 해당 필드를 받는 생성자가 있어야 하며, 클래스 전체 경로를 명시해야 한다
 */
List<EmployeeDTO> results = session.createQuery("select new com.example.dto.EmployeeDTO(e.empId, e.empName, e.gender, c.courseName, d.deptName) from Employee e left join e.course c left join c.department d")
                            .list();

12. GROUP BY 및 HAVING 절

/**
 * HQL에서는 SELECT 절의 필드에 별칭을 줄 수 없고, FROM 절 이후에만 별칭 사용 가능하다
 */
// employees가 150명 이상인 부서 조회
List<Object[]> results = session.createQuery("select d.deptName, count(e.course.department.deptId) from Employee e right join e.course.department d group by d.deptId having count(e.course.department.deptId) >= 150")
                        .list();

// 부서별 성별에 따른 직원 수 조회
List<Object[]> results = session.createQuery("select d.deptName, e.gender, count(e.course.department.deptId) from Employee e right join e.course.department d group by d.deptId, e.gender")
                        .list();

7월 18일 01:57에 게시됨