이 글에서는 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();