0%

hibernate检索方式

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();

// 1. 先加载主对象(客户)
Customer customer = session.get(Customer.class, 1L);

// 2. 通过关联属性获取关联对象(订单集合)
Set<Order> orders = customer.getOrders();

// 3. 访问关联对象(触发延迟加载,执行SQL)
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();

// 1. get() 方法:立即加载,返回真实对象
Customer customer1 = session.get(Customer.class, 1L);
System.out.println(customer1 == null ? "不存在" : "客户名:" + customer1.getName());

// 2. load() 方法:延迟加载,返回代理对象
Customer customer2 = session.load(Customer.class, 2L);
System.out.println("未访问属性,未执行SQL");
System.out.println("客户名:" + customer2.getName()); // 首次访问属性,执行SQL

tx.commit();

3. HQL 检索方式(Hibernate Query Language)

核心逻辑

HQL 是面向对象的查询语言,语法类似 SQL,但操作的是Java 类名属性名(而非数据库表名和字段名),Hibernate 会根据数据库方言自动转换为原生 SQL,支持跨数据库兼容。

(1)基础查询(查询所有对象)
1
2
3
// HQL:from 类名(全类名或默认包名简写)
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
// 1. 按名称绑定(推荐)
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();

// 2. 按位置绑定
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;    // 第2页
int pageSize = 10; // 每页10条
List<Customer> pageData = session.createQuery("from Customer order by id desc", Customer.class)
.setFirstResult((pageNo - 1) * pageSize) // 起始位置:(2-1)*10=10
.setMaxResults(pageSize) // 每页条数
.list();
(4)投影查询(查询部分字段)

默认返回 Object[] 数组,可通过构造器映射结果转换器转为实体对象:

方式 1:返回 Object[](适用于简单场景)
1
2
3
4
5
6
7
8
9
10
11
12
// 查询id和name字段,返回Object[]
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
// 1. 实体类添加含参构造器(必须保留无参构造器)
public class Customer {
private Long id;
private String name;
private Integer age;

// 含参构造器(用于投影查询)
public Customer(Long id, String name) {
this.id = id;
this.name = name;
}

// 无参构造器(Hibernate必须)
public Customer() {}
// getter/setter
}

// 2. HQL中使用new关键字调用构造器
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)
// 结果转换器:将字段映射到Customer属性
.setResultTransformer(Transformers.aliasToBean(Customer.class))
.list();
(5)聚合查询(count、sum、avg 等)

使用 uniqueResult() 获取单个聚合结果(如总数、平均值):

1
2
3
4
5
6
7
8
9
// 1. 统计客户总数
String countHql = "select count(id) from Customer";
Long total = session.createQuery(countHql, Long.class).uniqueResult();
System.out.println("客户总数:" + total);

// 2. 统计平均年龄
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 的无字符串查询方式,通过 CriteriaRestrictions 等类构建查询条件,完全面向对象,支持动态条件拼接(如根据前端参数动态添加筛选条件),类型安全且避免 SQL 注入。

(1)基础查询(单条件筛选)
1
2
3
4
5
6
7
8
9
// 1. 创建Criteria对象(指定查询的实体类)
Criteria criteria = session.createCriteria(Customer.class);

// 2. 添加查询条件(id=1,使用Restrictions工具类)
criteria.add(Restrictions.eq("id", 1L));

// 3. 执行查询(uniqueResult()返回单个结果,list()返回集合)
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 criteria = session.createCriteria(Customer.class);

// 动态添加条件:若minAge不为null,添加年龄>=minAge
if (minAge != null) {
criteria.add(Restrictions.ge("age", minAge));
}

// 动态添加条件:若nameKeyword不为null,添加姓名模糊匹配
if (nameKeyword != null && !nameKeyword.isEmpty()) {
criteria.add(Restrictions.like("name", "%" + nameKeyword + "%"));
}

// 动态添加条件:若isVip为true,添加VIP标识筛选
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);

// 1. 排序(按id降序,使用Order工具类)
criteria.addOrder(Order.desc("id"));

// 2. 分页(第2页,每页10条)
int pageNo = 2;
int pageSize = 10;
criteria.setFirstResult((pageNo - 1) * pageSize);
criteria.setMaxResults(pageSize);

// 3. 执行查询
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
// 原生SQL(MySQL语法,如LIMIT)
String sql = "SELECT id, name, age FROM t_customer WHERE age > ? LIMIT ?";

// 创建SQLQuery对象
List<Object[]> result = session.createSQLQuery(sql)
.setParameter(0, 18) // 绑定参数1:年龄>18
.setParameter(1, 10) // 绑定参数2:最多返回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)
// 1. 显式指定字段类型(避免Hibernate类型推断错误)
.addScalar("id", StandardBasicTypes.LONG)
.addScalar("name", StandardBasicTypes.STRING)
.addScalar("age", StandardBasicTypes.INTEGER)
// 2. 绑定参数
.setParameter("minId", 1L)
// 3. 结果映射为Customer对象
.setResultTransformer(Transformers.aliasToBean(Customer.class))
.list();
(3)调用存储过程
1
2
3
4
5
6
7
8
9
// 调用存储过程:获取指定客户的订单总数(存储过程名:proc_get_order_count)
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、存储过程、数据库特有功能 ⭐⭐⭐

选择建议

  1. 简单单对象查询:优先用 OID 检索(get()/load());
  2. 关联对象查询:用导航对象图检索(依赖关联关系);
  3. 复杂固定条件查询:用 HQL(语法简洁,跨数据库);
  4. 动态条件查询:用 QBC(类型安全,无需拼接字符串);
  5. 数据库特有功能:用本地 SQL(如存储过程、MySQL 的 LIMIT、Oracle 的 ROWNUM)。

通用优化建议

  1. 参数绑定优先:无论 HQL 还是本地 SQL,均使用 setParameter() 绑定参数,避免字符串拼接导致的 SQL 注入风险;
  2. 分页必用:查询大量数据时,通过 setFirstResult()setMaxResults() 分页,避免内存溢出;
  3. 避免 SELECT *:投影查询仅获取必要字段,减少数据传输量;
  4. 合理使用索引:确保查询条件中的字段(如 whereorder by 后的字段)已建立索引,提升 SQL 执行效率;
  5. 监控 SQL 性能:开启 Hibernate 的 show_sqlformat_sql,分析生成的 SQL 是否最优,必要时用本地 SQL 优化。

总结

Hibernate 提供的五种检索方式覆盖了从简单到复杂的所有查询场景,核心是根据业务需求选择合适的方式:

  • 面向对象的方式(HQL、QBC)兼顾跨数据库兼容性和开发效率;
  • 原生 SQL 方式则在性能和功能灵活性上更具优势;
  • 导航和 OID 检索则是最简单直接的查询手段

欢迎关注我的其它发布渠道

表情 | 预览
Powered By Valine
v1.3.10