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)
⚠️ 即使是更新操作,也需要指定 resultClass
或 resultSetMapping
,因为 Hibernate 不支持纯原生标量查询。
6. 总结
本文介绍了 Hibernate 中命名查询的使用方法,包括:
- 使用
@NamedQuery
定义 HQL 命名查询 - 使用
@NamedNativeQuery
定义原生 SQL 命名查询 - 查询特性的配置
- 如何在代码中调用这些命名查询
- 对存储过程的支持
这些机制有助于提升代码的可读性和可维护性,值得在实际项目中推广使用。
📌 示例代码已上传至 GitHub: https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate-queries