Hibernate 쿼리 완벽 가이드 - HQL과 Criteria API
Source: Dev.to
HQL (Hibernate Query Language)
HQL은 SQL과 유사하지만, 테이블이 아닌 Entity 객체를 대상으로 쿼리합니다. 데이터베이스에 독립적이며 객체‑지향적인 쿼리 작성이 가능합니다.
1. 기본 조회 (Select)
// 전체 조회
Query query = session.createQuery("from Emp");
List list = query.list();
// 페이징 처리
Query query = session.createQuery("from Emp");
query.setFirstResult(5); // 시작 위치 (0부터 시작)
query.setMaxResults(10); // 조회할 개수
List list = query.list(); // 6번째부터 10개 조회
HQL에서는 테이블명이 아닌 클래스명을 사용합니다.
SELECT * FROM emp대신from Emp로 작성합니다.
2. 조건 조회 (Where)
// 파라미터 바인딩 (Named Parameter)
Query query = session.createQuery(
"from Emp where salary > :minSalary"
);
query.setParameter("minSalary", 50000);
List results = query.list();
// 여러 조건
Query query = session.createQuery(
"from Emp e where e.department = :dept and e.salary > :salary"
);
query.setParameter("dept", "Engineering");
query.setParameter("salary", 40000);
3. 수정 (Update)
Transaction tx = session.beginTransaction();
Query query = session.createQuery(
"update User set name = :name where id = :id"
);
query.setParameter("name", "김철수");
query.setParameter("id", 111);
int affectedRows = query.executeUpdate();
System.out.println("수정된 행 수: " + affectedRows);
tx.commit();
UPDATE는 벌크 연산으로 영속성 컨텍스트를 거치지 않고 직접 DB를 수정합니다.
주의: 벌크 연산 후session.clear()로 영속성 컨텍스트를 초기화하는 것이 좋습니다. 그렇지 않으면 캐시된 데이터와 실제 DB 값이 불일치할 수 있습니다.
4. 삭제 (Delete)
Transaction tx = session.beginTransaction();
Query query = session.createQuery(
"delete from Emp where id = :empId"
);
query.setParameter("empId", 100);
query.executeUpdate();
tx.commit();
HQL에서는 테이블명이 아닌 클래스명(
Emp)을 사용합니다.
5. 집계 함수 (Aggregate Functions)
// SUM
Query query = session.createQuery("select sum(salary) from Emp");
Long totalSalary = (Long) query.uniqueResult();
// COUNT
Query query = session.createQuery(
"select count(*) from Emp where department = :dept"
);
query.setParameter("dept", "Engineering");
Long count = (Long) query.uniqueResult();
// 여러 집계 함수
Query query = session.createQuery(
"select min(salary), max(salary), avg(salary) from Emp"
);
Object[] result = (Object[]) query.uniqueResult();
6. 그룹화와 정렬
// GROUP BY
Query query = session.createQuery(
"select department, avg(salary) from Emp group by department"
);
List results = query.list();
// ORDER BY
Query query = session.createQuery(
"from Emp order by salary desc, name asc"
);
// GROUP BY + HAVING
Query query = session.createQuery(
"select department, count(*) from Emp " +
"group by department having count(*) > 5"
);
7. 조인 (Join)
// Inner Join
Query query = session.createQuery(
"select e.name, d.name from Emp e join e.department d"
);
// Left Join (fetch)
Query query = session.createQuery(
"from Emp e left join fetch e.address"
);
// Fetch Join – 연관 엔티티를 함께 조회 (N+1 문제 해결)
Query query = session.createQuery(
"from Emp e join fetch e.orders"
);
Criteria API (HCQL)
Criteria API는 프로그래밍 방식으로 쿼리를 작성합니다. 동적 쿼리에 특히 유용합니다.
1. 기본 사용법 (전체 조회)
Criteria criteria = session.createCriteria(Emp.class);
List employees = criteria.list();
2. 조건 추가 (Restrictions)
Criteria criteria = session.createCriteria(Emp.class);
// 단일 조건
criteria.add(Restrictions.eq("name", "홍길동"));
// 여러 조건 (AND)
criteria.add(Restrictions.gt("salary", 40000));
criteria.add(Restrictions.eq("department", "Engineering"));
// OR 조건
criteria.add(Restrictions.or(
Restrictions.eq("department", "Engineering"),
Restrictions.eq("department", "Sales")
));
List results = criteria.list();
주요 Restriction 메서드
| 메서드 | 설명 | SQL 연산자 |
|---|---|---|
eq(property, value) | 같음 | = |
ne(property, value) | 같지 않음 | <> |
gt(property, value) | 초과 | > |
ge(property, value) | 이상 | >= |
lt(property, value) | 미만 | < |
le(property, value) | 이하 | <= |
like(property, value) | 패턴 매칭 | LIKE |
in(property, values) | 포함 | IN |
3. 정렬 (Order)
criteria.addOrder(Order.asc("name"));
criteria.addOrder(Order.desc("salary"));
4. 페이징 (Pagination)
Criteria criteria = session.createCriteria(Emp.class);
criteria.setFirstResult(0); // 시작 위치
criteria.setMaxResults(10); // 조회 개수
List results = criteria.list();
5. 프로젝션 (Projection) – 특정 필드만 조회
Criteria criteria = session.createCriteria(Emp.class);
criteria.setProjection(Projections.projectionList()
.add(Projections.property("name"))
.add(Projections.property("salary"))
);
List results = criteria.list();
6. 집계 함수 (Aggregate Functions)
Criteria criteria = session.createCriteria(Emp.class);
// 단일 집계
criteria.setProjection(Projections.rowCount());
Long count = (Long) criteria.uniqueResult();
// 그룹별 집계
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("department"))
.add(Projections.avg("salary"))
);
List results = criteria.list();
JPA 2.0 Criteria API
JPA 2.0부터는 표준 Criteria API가 도입되었습니다. Hibernate Criteria보다 타입 안전합니다.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Emp> query = cb.createQuery(Emp.class);
Root<Emp> emp = query.from(Emp.class);
// 조건 추가
query.select(emp)
.where(
cb.and(
cb.equal(emp.get("department"), "Engineering"),
cb.gt(emp.get("salary"), 40000)
)
)
.orderBy(cb.desc(emp.get("salary")));
List<Emp> results = entityManager.createQuery(query).getResultList();
HQL vs Criteria API 비교
| 특징 | HQL | Criteria API |
|---|---|---|
| 문법 | SQL과 유사한 문자열 | 프로그래밍 방식 |
| 동적 쿼리 | 문자열 조합 필요 | 메서드 체이닝으로 편리 |
| 타입 안전성 | 런타임 오류 | 컴파일 타임 검증 가능 |
| 가독성 | 익숙한 SQL 스타일 | 복잡한 쿼리는 코드가 길어짐 |
| 사용 시점 | 정적 쿼리, 간단한 쿼리 | 동적 쿼리, 조건이 많은 쿼리 |
선택 가이드
- HQL 추천: 쿼리가 정적이고 변경이 적은 경우, SQL에 익숙한 팀
- Criteria API 추천: 검색 조건이 동적으로 변하는 경우, 타입 안전성이 중요한 경우
Spring Data JPA에서의 활용
Spring Data JPA에서는 메서드 이름으로 쿼리를 생성하거나 @Query 어노테이션을 사용합니다.
public interface EmpRepository extends JpaRepository<Emp, Long> {
// 메서드 이름으로 쿼리 생성
List<Emp> findByDepartmentAndSalaryGreaterThan(String department, int salary);
// JPQL 직접 작성
@Query("select e from Emp e where e.department = :dept order by e.salary desc")
List<Emp> findByDepartmentOrderBySalary(@Param("dept") String department);
// Native Query
@Query(value = "SELECT * FROM emp WHERE salary > ?1", nativeQuery = true)
List<Emp> findHighSalaryEmployees(int minSalary);
}
동적 쿼리가 필요한 경우 Specification을 활용합니다:
public interface EmpRepository extends JpaRepository<Emp, Long>,
JpaSpecificationExecutor<Emp> {
}
// Specification 정의
public class EmpSpecifications {
public static Specification<Emp> hasDepartment(String department) {
return (root, query, cb) -> cb.equal(root.get("department"), department);
}
public static Specification<Emp> salaryGreaterThan(int salary) {
return (root, query, cb) -> cb.gt(root.get("salary"), salary);
}
}
// 사용
List<Emp> results = empRepository.findAll(
Specification.where(EmpSpecifications.hasDepartment("Engineering"))
.and(EmpSpecifications.salaryGreaterThan(40000))
);
결론
Hibernate의 쿼리 방식을 정리하면:
- HQL: SQL과 유사한 문법으로 객체 중심 쿼리 작성
- Criteria API: 프로그래밍 방식으로 동적 쿼리에 적합
- Spring Data JPA: 메서드 이름이나
@Query로 간편하게 쿼리 정의
상황에 맞는 쿼리 방식을 선택하여 유지보수하기 쉬운 코드를 작성하세요. 복잡한 동적 쿼리는 Criteria API나 QueryDSL을 활용하는 것이 좋습니다.