1. 概述

本文将深入探讨JPA和Hibernate查询的使用场景及区别,重点分析Criteria、JPQL和HQL三种查询方式。Criteria查询允许开发者在不编写原生SQL的情况下构建查询语句。除Criteria查询外,我们还将研究Hibernate命名查询的实现方式,以及Spring Data JPA中@Query注解的使用技巧。

需要特别说明的是:Hibernate Criteria API自5.2版本起已被弃用。因此**本文所有示例均采用JPA Criteria API**,这是目前编写Criteria查询的推荐方案。下文提到的"Criteria API"均指JPA标准实现。

2. Criteria查询

Criteria API的核心功能是通过动态添加过滤条件和逻辑运算来构建查询对象。这是一种面向对象的数据操作方式,可直接从关系型数据库表中获取目标数据。

Hibernate的Session对象通过*createCriteria()*方法返回持久化实例,用于执行Criteria查询。简单来说,Criteria API通过叠加过滤条件和逻辑规则来构建最终查询语句。

2.1 Maven依赖

添加最新的JPA实现依赖pom.xml

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.4.2.Final</version>
</dependency>

2.2 Criteria查询与表达式使用

CriteriaBuilder负责控制查询结果,它通过CriteriaQuery的*where()*方法应用各种表达式。

先看本文使用的实体类:

public class Employee {
    private Integer id;
    private String name;
    private Long salary;
    
    // 标准getter/setter方法
}

下面是一个获取所有员工记录的简单Criteria查询:

Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> cr = cb.createQuery(Employee.class);
Root<Employee> root = cr.from(Employee.class);
cr.select(root);

Query<Employee> query = session.createQuery(cr);
List<Employee> results = query.getResultList();
session.close();
return results;

执行流程解析:

  1. SessionFactory创建Session实例
  2. Session通过getCriteriaBuilder()获取CriteriaBuilder实例
  3. CriteriaBuilder使用createQuery()创建CriteriaQuery对象
  4. 最终调用*getResultList()*获取结果集

再看一个带条件的表达式示例:

cr.select(root).where(cb.gt(root.get("salary"), 50000));

该查询返回薪资超过50000的所有员工记录。

3. JPQL查询

JPQL(Java Persistence Query Language)是Spring Data提供的查询方案之一。通过*@Query*注解可同时执行JPQL和原生SQL查询,默认使用JPQL语法

在Spring中,**@Query定义的查询优先级高于@NamedQuery标注的命名查询**。

3.1 JPQL查询使用

构建动态JPQL查询示例:

@Query(value = "SELECT e FROM Employee e")
List<Employee> findAllEmployees(Sort sort);

带参数的JPQL查询中,Spring Data会按方法参数顺序传递参数:

@Query("SELECT e FROM Employee e WHERE e.salary = ?1")
Employee findAllEmployeesWithSalary(Long salary);
@Query("SELECT e FROM Employee e WHERE e.name = ?1 and e.salary = ?2")
Employee findEmployeeByNameAndSalary(String name, Long salary);

第二个查询中,name参数对应索引1,salary参数对应索引2。

3.2 JPQL原生查询

原生查询直接操作真实数据库表对象,需设置nativeQuery=true

@Query(
  value = "SELECT * FROM Employee e WHERE e.salary = ?1",
  nativeQuery = true)
Employee findEmployeeBySalaryNative(Long salary);

使用命名参数可提升查询可读性并减少重构风险,下面是JPQL和原生格式的命名参数示例:

@Query("SELECT e FROM Employee e WHERE e.name = :name and e.salary = :salary")
Employee findEmployeeByNameAndSalaryNamedParameters(
  @Param("name") String name,
  @Param("salary") Long salary);

方法参数通过@Param注解映射到查询参数,注解值必须与查询中的参数名完全匹配

原生查询的命名参数版本:

@Query(value = "SELECT * FROM Employee e WHERE e.name = :name and e.salary = :salary", 
  nativeQuery = true) 
Employee findUserByNameAndSalaryNamedParamsNative( 
  @Param("name") String name, 
  @Param("salary") Long salary);

4. HQL查询

HQL(Hibernate Query Language)是面向对象的类SQL查询语言。主要缺点是代码可读性较差,通常通过命名查询将SQL语句与业务代码分离。

4.1 Hibernate命名查询

命名查询使用预定义的不可变查询字符串,在SessionFactory创建时即进行验证(快速失败机制)。使用org.hibernate.annotations.NamedQuery定义:

@NamedQuery(name = "Employee_FindByEmployeeId",
 query = "from Employee where id = :id")

每个@NamedQuery仅关联一个实体类,使用@NamedQueries可组合多个查询:

@NamedQueries({
    @NamedQuery(name = "Employee_findByEmployeeId", 
      query = "from Employee where id = :id"),
    @NamedQuery(name = "Employee_findAllByEmployeeSalary", 
      query = "from Employee where salary = :salary")
})

4.2 存储过程与表达式

使用*@NamedNativeQuery*注解存储过程和函数:

@NamedNativeQuery(
  name = "Employee_FindByEmployeeId", 
  query = "select * from employee emp where id=:id", 
  resultClass = Employee.class)

5. Criteria查询的优势

Criteria API相比HQL的最大优势是提供清晰、面向对象的API,能在编译阶段检测错误。

此外,JPQL和Criteria查询具有相同的性能表现。

Criteria查询在构建动态查询时比HQL/JPQL更灵活,但存在以下局限:

  • 不支持原生查询(HQL/JPQL支持)
  • 复杂关联查询实现较繁琐(JPQL原生查询更易处理)

6. 总结

本文系统介绍了JPA/Hibernate中三种核心查询方式:

  • Criteria API:动态查询构建利器
  • JPQL:Spring Data生态的首选查询方案
  • HQL:Hibernate原生查询语言

每种方案都有其适用场景,开发者需根据实际需求权衡选择。完整代码示例可在GitHub仓库此处获取。


原始标题:JPA and Hibernate – Criteria vs. JPQL vs. HQL Query