Hibernate 检索方式全解析:从导航查询到本地 SQL 的实战指南 Hibernate 提供多种灵活的对象检索方式,覆盖从简单的单对象查询到复杂的多条件筛选场景。这些方式可分为对象导航检索、OID 检索、HQL 检索、QBC 检索、本地 SQL 检索 五类,每类适用于不同业务需求。本文结合代码示例,详细解析每种检索方式的实现逻辑、核心 API 及最佳实践,帮助开发者高效选择查询方案。
检索方式分类与核心场景 Hibernate 检索方式的设计遵循 “从简单到复杂、从面向对象到原生 SQL” 的梯度,各类方式的核心定位如下:
检索方式
核心原理
适用场景
优势
导航对象图检索
通过已加载对象的关联属性获取关联对象
已知主对象,需访问其关联对象(如订单→客户)
无需手动写查询,依赖对象关系自动加载
OID 检索
通过主键(OID)直接获取对象
已知主键,需快速查询单个对象
最简单高效,直接定位记录
HQL 检索
面向对象的查询语言(类似 SQL,操作对象而非表)
复杂多条件查询(如多表联查、聚合统计)
跨数据库兼容,支持动态参数绑定
QBC 检索
基于 API 的无字符串查询(链式调用构建条件)
动态条件查询(如条件数量不确定的筛选)
类型安全,避免 SQL 注入,无需拼接字符串
本地 SQL 检索
使用数据库原生 SQL 查询
复杂 SQL 场景(如存储过程、特殊函数调用)
充分利用数据库特性,性能最优
详细检索方式解析 1. 导航对象图检索方式 核心逻辑 基于对象间的关联关系(如一对多、多对一),通过已加载对象的关联属性 直接获取关联对象,无需手动编写查询语句,依赖 Hibernate 的关联检索策略(延迟 / 立即加载)。
代码示例(一对多关联:客户→订单)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Session session = sessionFactory.getCurrentSession(); Transaction tx = session.beginTransaction(); Customer customer = session.get(Customer.class, 1L ); Set<Order> orders = customer.getOrders(); for (Order order : orders) { System.out.println("订单编号:" + order.getOrderNo()); } tx.commit();
关键注意事项
依赖关联检索策略:若关联配置为 lazy="true"(默认),访问 customer.getOrders() 时才执行 SQL 加载订单;
避免懒加载异常:若 Session 已关闭,访问未初始化的关联集合会抛 LazyInitializationException,需在 Session 关闭前初始化(如 Hibernate.initialize(customer.getOrders()))。
2. OID 检索方式 核心逻辑 通过对象的主键(OID) 直接检索对象,核心 API 为 session.get() 和 session.load(),是最简单的单对象查询方式。
(1)session.get(Class<T> cls, Serializable id)
行为 :立即执行 SQL,返回真实对象(非代理),若不存在对应记录则返回 null;
适用场景 :需确认对象是否存在,或 Session 可能提前关闭(无懒加载风险)。
(2)session.load(Class<T> cls, Serializable id)
行为 :默认延迟加载,返回代理对象(仅初始化 OID),首次访问非 OID 属性时执行 SQL;若不存在对应记录则抛 ObjectNotFoundException;
适用场景 :已知对象一定存在,且可在 Session 关闭前访问属性(如 Service 层内部查询)。
代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 Session session = sessionFactory.getCurrentSession(); Transaction tx = session.beginTransaction(); Customer customer1 = session.get(Customer.class, 1L ); System.out.println(customer1 == null ? "不存在" : "客户名:" + customer1.getName()); Customer customer2 = session.load(Customer.class, 2L ); System.out.println("未访问属性,未执行SQL" ); System.out.println("客户名:" + customer2.getName()); tx.commit();
3. HQL 检索方式(Hibernate Query Language) 核心逻辑 HQL 是面向对象的查询语言,语法类似 SQL,但操作的是Java 类名 和属性名 (而非数据库表名和字段名),Hibernate 会根据数据库方言自动转换为原生 SQL,支持跨数据库兼容。
(1)基础查询(查询所有对象) 1 2 3 String hql = "from Customer" ; List<Customer> customers = session.createQuery(hql, Customer.class).list();
(2)参数绑定(避免 SQL 注入) HQL 支持两种参数绑定方式,优先使用按名称绑定 (可读性更高,参数顺序无关):
绑定方式
语法
API 示例
按名称绑定
:参数名
setParameter("id", 1L)
按位置绑定
?(从 0 开始计数)
setParameter(0, 1L)
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 String hql1 = "from Customer where id > :minId and name like :namePattern" ; List<Customer> list1 = session.createQuery(hql1, Customer.class) .setParameter("minId" , 1L ) .setParameter("namePattern" , "%张%" ) .list(); String hql2 = "from Customer where id > ? and name like ?" ; List<Customer> list2 = session.createQuery(hql2, Customer.class) .setParameter(0 , 1L ) .setParameter(1 , "%张%" ) .list();
(3)分页查询 通过 setFirstResult()(起始位置,从 0 开始)和 setMaxResults()(每页条数)实现分页:
1 2 3 4 5 6 int pageNo = 2 ; int pageSize = 10 ; List<Customer> pageData = session.createQuery("from Customer order by id desc" , Customer.class) .setFirstResult((pageNo - 1 ) * pageSize) .setMaxResults(pageSize) .list();
(4)投影查询(查询部分字段) 默认返回 Object[] 数组,可通过构造器映射 或结果转换器 转为实体对象:
方式 1:返回 Object[](适用于简单场景) 1 2 3 4 5 6 7 8 9 10 11 12 String hql = "select id, name from Customer where id > :minId" ; List<Object[]> result = session.createQuery(hql) .setParameter("minId" , 1L ) .list(); for (Object[] objArr : result) { Long id = (Long) objArr[0 ]; String name = (String) objArr[1 ]; System.out.println("ID:" + id + ",姓名:" + name); }
方式 2:构造器映射(需实体类有对应构造器) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Customer { private Long id; private String name; private Integer age; public Customer (Long id, String name) { this .id = id; this .name = name; } public Customer () {} } String hql = "select new Customer(id, name) from Customer where id > :minId" ; List<Customer> customers = session.createQuery(hql, Customer.class) .setParameter("minId" , 1L ) .list();
方式 3:结果转换器(适用于复杂投影或原生 SQL) 通过 Transformers.aliasToBean() 将查询结果映射为实体对象,需确保查询字段名与实体类属性名一致:
1 2 3 4 5 6 String hql = "select id as id, name as name from Customer where id > :minId" ; List<Customer> customers = session.createQuery(hql) .setParameter("minId" , 1L ) .setResultTransformer(Transformers.aliasToBean(Customer.class)) .list();
(5)聚合查询(count、sum、avg 等) 使用 uniqueResult() 获取单个聚合结果(如总数、平均值):
1 2 3 4 5 6 7 8 9 String countHql = "select count(id) from Customer" ; Long total = session.createQuery(countHql, Long.class).uniqueResult(); System.out.println("客户总数:" + total); String avgHql = "select avg(age) from Customer where age is not null" ; Double avgAge = session.createQuery(avgHql, Double.class).uniqueResult(); System.out.println("平均年龄:" + avgAge);
4. QBC 检索方式(Query By Criteria) 核心逻辑 QBC 是基于 API 的无字符串查询方式,通过 Criteria 和 Restrictions 等类构建查询条件,完全面向对象,支持动态条件拼接(如根据前端参数动态添加筛选条件),类型安全且避免 SQL 注入。
(1)基础查询(单条件筛选) 1 2 3 4 5 6 7 8 9 Criteria criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("id" , 1L )); Customer customer = (Customer) criteria.uniqueResult(); System.out.println("客户名:" + customer.getName());
(2)常用查询条件(Restrictions 工具类)
条件类型
方法示例
说明
等于
Restrictions.eq("name", "张三")
字段值等于指定值
不等于
Restrictions.ne("age", 20)
字段值不等于指定值
大于
Restrictions.gt("age", 18)
字段值大于指定值
小于等于
Restrictions.le("age", 30)
字段值小于等于指定值
模糊查询
Restrictions.like("name", "%张%")
字段值模糊匹配
范围查询
Restrictions.between("age", 18, 30)
字段值在指定范围内
非空
Restrictions.isNotNull("email")
字段值不为 null
逻辑与
Restrictions.and(gt, le)
多个条件同时满足
逻辑或
Restrictions.or(like, between)
多个条件满足其一
(3)动态条件查询(示例:多条件筛选) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Integer minAge = 18 ; String nameKeyword = "张" ; Boolean isVip = true ; Criteria criteria = session.createCriteria(Customer.class); if (minAge != null ) { criteria.add(Restrictions.ge("age" , minAge)); } if (nameKeyword != null && !nameKeyword.isEmpty()) { criteria.add(Restrictions.like("name" , "%" + nameKeyword + "%" )); } if (isVip != null && isVip) { criteria.add(Restrictions.eq("isVip" , true )); } List<Customer> customers = criteria.list();
(4)分页与排序 1 2 3 4 5 6 7 8 9 10 11 12 13 Criteria criteria = session.createCriteria(Customer.class); criteria.addOrder(Order.desc("id" )); int pageNo = 2 ;int pageSize = 10 ;criteria.setFirstResult((pageNo - 1 ) * pageSize); criteria.setMaxResults(pageSize); List<Customer> pageData = criteria.list();
5. 本地 SQL 检索方式 核心逻辑 直接使用数据库原生 SQL 语句查询,适用于 HQL/QBC 无法满足的复杂场景(如存储过程、特殊函数、多表复杂联查),但失去跨数据库兼容性 (需根据数据库调整 SQL)。
(1)基础查询(返回 Object[]) 1 2 3 4 5 6 7 8 9 10 11 12 13 String sql = "SELECT id, name, age FROM t_customer WHERE age > ? LIMIT ?" ; List<Object[]> result = session.createSQLQuery(sql) .setParameter(0 , 18 ) .setParameter(1 , 10 ) .list(); for (Object[] objArr : result) { System.out.println("ID:" + objArr[0 ] + ",姓名:" + objArr[1 ]); }
(2)映射为实体对象(需指定字段类型) 通过 addScalar() 显式指定字段类型,结合 Transformers.aliasToBean() 映射为实体对象:
1 2 3 4 5 6 7 8 9 10 11 12 String sql = "SELECT id, name, age FROM t_customer WHERE id > :minId" ; List<Customer> customers = session.createSQLQuery(sql) .addScalar("id" , StandardBasicTypes.LONG) .addScalar("name" , StandardBasicTypes.STRING) .addScalar("age" , StandardBasicTypes.INTEGER) .setParameter("minId" , 1L ) .setResultTransformer(Transformers.aliasToBean(Customer.class)) .list();
(3)调用存储过程 1 2 3 4 5 6 7 8 9 String sql = "CALL proc_get_order_count(:customerId)" ; Long orderCount = (Long) session.createSQLQuery(sql) .setParameter("customerId" , 1L ) .uniqueResult(); System.out.println("客户1的订单总数:" + orderCount);
检索方式对比与选择建议
检索方式
跨数据库兼容
动态条件支持
学习成本
适用场景
推荐度
导航对象图检索
是
无
低
已知主对象,需访问关联对象
⭐⭐⭐⭐
OID 检索
是
无
低
已知主键,查询单个对象
⭐⭐⭐⭐⭐
HQL 检索
是
中
中
复杂多条件查询、聚合统计
⭐⭐⭐⭐⭐
QBC 检索
是
高
中
动态条件查询(如前端多筛选参数)
⭐⭐⭐⭐
本地 SQL 检索
否
高
高
复杂 SQL、存储过程、数据库特有功能
⭐⭐⭐
选择建议
简单单对象查询 :优先用 OID 检索(get()/load());
关联对象查询 :用导航对象图检索(依赖关联关系);
复杂固定条件查询 :用 HQL(语法简洁,跨数据库);
动态条件查询 :用 QBC(类型安全,无需拼接字符串);
数据库特有功能 :用本地 SQL(如存储过程、MySQL 的 LIMIT、Oracle 的 ROWNUM)。
通用优化建议
参数绑定优先 :无论 HQL 还是本地 SQL,均使用 setParameter() 绑定参数,避免字符串拼接导致的 SQL 注入风险;
分页必用 :查询大量数据时,通过 setFirstResult() 和 setMaxResults() 分页,避免内存溢出;
避免 SELECT * :投影查询仅获取必要字段,减少数据传输量;
合理使用索引 :确保查询条件中的字段(如 where、order by 后的字段)已建立索引,提升 SQL 执行效率;
监控 SQL 性能 :开启 Hibernate 的 show_sql 和 format_sql,分析生成的 SQL 是否最优,必要时用本地 SQL 优化。
总结 Hibernate 提供的五种检索方式覆盖了从简单到复杂的所有查询场景,核心是根据业务需求选择合适的方式:
面向对象的方式(HQL、QBC)兼顾跨数据库兼容性和开发效率;
原生 SQL 方式则在性能和功能灵活性上更具优势;
导航和 OID 检索则是最简单直接的查询手段
v1.3.10