1. 概述

在数据访问层中,如果 HQL 或 SQL 查询语句分散在各个 DAO 类中,会导致代码可读性差,维护成本高。为了解决这个问题,Hibernate 提供了 Named Query(命名查询) 机制,允许我们将所有查询语句集中定义,然后在业务逻辑中通过名称引用。

✅ Named Query 是一种静态定义的查询,其查询字符串在编译时就已经确定。Hibernate 在创建 SessionFactory 时会对其进行校验,一旦语法错误会立即暴露,实现快速失败。

在本文中,我们将介绍如何使用 @NamedQuery@NamedNativeQuery 注解来定义和使用 Hibernate 命名查询。

2. 实体类示例

我们以如下实体类为例进行演示:

@Entity
public class DeptEmployee {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String employeeNumber;

    private String designation;

    private String name;

    @ManyToOne
    private Department department;

    // getters and setters
}

我们的目标是通过员工编号(employeeNumber)查询对应的员工信息。

3. 定义 Named Query

Hibernate 提供了 @org.hibernate.annotations.NamedQuery 注解用于定义命名查询。该注解扩展了 JPA 的 javax.persistence.NamedQuery,并加入了一些 Hibernate 特性。

我们将其定义为 DeptEmployee 类上的注解:

@org.hibernate.annotations.NamedQuery(
  name = "DeptEmployee_findByEmployeeNumber", 
  query = "from DeptEmployee where employeeNumber = :employeeNo"
)

⚠️ 注意:每个 @NamedQuery 注解只能绑定在一个实体类或映射超类上。但由于命名查询的作用域是整个持久化单元,所以命名时建议加上实体类前缀,避免命名冲突。

如果一个实体类有多个命名查询,可以使用 @NamedQueries 注解进行分组:

@org.hibernate.annotations.NamedQueries({
    @org.hibernate.annotations.NamedQuery(
      name = "DeptEmployee_FindByEmployeeNumber", 
      query = "from DeptEmployee where employeeNumber = :employeeNo"),
    @org.hibernate.annotations.NamedQuery(
      name = "DeptEmployee_FindAllByDesignation", 
      query = "from DeptEmployee where designation = :designation"),
    @org.hibernate.annotations.NamedQuery(
      name = "DeptEmployee_UpdateEmployeeDepartment", 
      query = "Update DeptEmployee set department = :newDepartment where employeeNumber = :employeeNo")
})

📌 注意:HQL 查询不仅限于 SELECT 语句,也可以是 DML 操作,比如上面的更新语句。

3.1. 配置查询特性

通过 @NamedQuery 注解还可以配置一些查询行为:

@org.hibernate.annotations.NamedQuery(
  name = "DeptEmployee_FindAllByDepartment", 
  query = "from DeptEmployee where department = :department",
  timeout = 1,
  fetchSize = 10
)

支持的配置项包括:

  • timeout: 查询超时时间(秒)
  • fetchSize: 每次从数据库中抓取的记录数
  • cacheable: 是否启用查询缓存
  • cacheMode: 缓存模式,可选:GET, IGNORE, NORMAL, PUT, REFRESH
  • cacheRegion: 查询缓存区域名称
  • comment: 添加到生成的 SQL 中的注释,方便 DBA 调优
  • flushMode: 刷新模式,可选:ALWAYS, AUTO, COMMIT, MANUAL, PERSISTENCE_CONTEXT

3.2. 使用 Named Query

定义完成后,就可以在代码中通过名称调用:

Query<DeptEmployee> query = session.createNamedQuery("DeptEmployee_FindByEmployeeNumber", DeptEmployee.class);
query.setParameter("employeeNo", "001");
DeptEmployee result = query.getSingleResult();

这里使用了 createNamedQuery 方法,它接收查询名称和返回类型,返回一个 org.hibernate.query.Query 对象。

4. Named Native Query(命名原生查询)

除了 HQL,Hibernate 也支持将原生 SQL 语句定义为命名查询,使用 @NamedNativeQuery 注解。

@NamedQuery 相比,原生查询需要额外指定结果映射方式。

示例:

@org.hibernate.annotations.NamedNativeQueries({
    @org.hibernate.annotations.NamedNativeQuery(
      name = "DeptEmployee_FindByEmployeeName", 
      query = "select * from deptemployee emp where name=:name",
      resultClass = DeptEmployee.class)
})

📌 由于是原生 SQL,必须告诉 Hibernate 如何将结果映射到实体类,这里有两种方式:

  • 使用 resultClass 属性指定实体类
  • 使用 resultSetMapping 指定预定义的 SQLResultSetMapping

⚠️ 注意:这两个属性只能选其一。

4.1. 使用 Named Native Query

使用方式与 HQL 命名查询类似:

Query<DeptEmployee> query = session.createNamedQuery("DeptEmployee_FindByEmployeeName", DeptEmployee.class);
query.setParameter("name", "John Wayne");
DeptEmployee result = query.getSingleResult();

或者使用 getNamedNativeQuery() 方法:

NativeQuery query = session.getNamedNativeQuery("DeptEmployee_FindByEmployeeName");
query.setParameter("name", "John Wayne");
DeptEmployee result = (DeptEmployee) query.getSingleResult();

两种方式的区别在于返回类型不同,后者返回的是 NativeQuery,它是 Query 的子类。

5. 存储过程与函数调用

通过 @NamedNativeQuery 还可以调用数据库中的存储过程或函数:

@org.hibernate.annotations.NamedNativeQuery(
  name = "DeptEmployee_UpdateEmployeeDesignation", 
  query = "call UPDATE_EMPLOYEE_DESIGNATION(:employeeNumber, :newDesignation)", 
  resultClass = DeptEmployee.class)

⚠️ 即使是更新操作,也需要指定 resultClassresultSetMapping,因为 Hibernate 不支持纯原生标量查询。

6. 总结

本文介绍了 Hibernate 中命名查询的使用方法,包括:

  • 使用 @NamedQuery 定义 HQL 命名查询
  • 使用 @NamedNativeQuery 定义原生 SQL 命名查询
  • 查询特性的配置
  • 如何在代码中调用这些命名查询
  • 对存储过程的支持

这些机制有助于提升代码的可读性和可维护性,值得在实际项目中推广使用。

📌 示例代码已上传至 GitHub: https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate-queries


原始标题:Hibernate Named Query | Baeldung