Hibernate 查询语言(HQL)
1. 前言
本节课程和大家一起学习 Hibernate 中的 HQL ( Hibernate 查询语言)。通过本节课程的学习,你将了解到:
- HQL 基础语法;
- HQL 查询的具体实现。-
2. HQL
查询?前面不是讲过?用过吗?
但是,前面的查询都是简单查询,真实项目中的查询需求要远比这个复杂。仅仅依靠 get()、load() 是远远达不到要求。
Hibernate 提供了灵活多样的查询机制,几乎能做到无死角查询。
- 标准化对象查询 (Criteria Query): 以对象的方式进行查询,将查询语句封装为对象操作。
- 优点: 可读性好,符合 Java 程序员的编码习惯。
- 缺点: 不够成熟,不支持投影(projection)或统计函数(aggregation)
Hibernate 语言查询(Hibernate Query Language,HQL): 它是完全面向对象的查询语句,查询功能非常强大,具有继承、多态和关联等特性 。Hibernate 官方推荐使用 HQL 进行查询。
Native SQL Queries(原生 SQL 查询): 直接使用数据库提供的 SQL 语句进行查询。
原生 SQL 的查询能力是最强的,当其它查询不能达到完成任务要求时,可使用它。
本次课程主要是介绍 HQL 查询。
2.1 HQL 基础语法
还是从查询需求入手吧。
查询需求:查询所有学生。
前提:确定数据库中有测试数据。
查询流程:
首先编写 HQL 语句,如:
select stuName,stuId from Student
HQL 整体结构上类似于 SQL,可以认为 HQL 语句由两部分组成,一部分直接借用 SQL 中的关键字,如 select、from、where 等,和 SQL 中要求是一样的,编写时可不区分大小写。
另一部分就是纯 JAVA 概念。
在 HQL 语句中,原生 SQL 语法中的表名被类名替换,字段名被属性名替换,类和属性是 Java 概念,所以要区分大小写。
下面的 HQL 语句用来查询所有学生信息,在 HQL 中同样有别名一说。
from Student s
HQL 是面向对象的、类似于 SQL 的查询语言,本质上是不能直接交给数据库的,在提交之前,需要把 HQL 转译成 SQL,在转译时把类名换成表名、把属性名换成字段名。
虽然替换工作由 Hibernate 自己完成,但是,你需要有所了解。
从中可得出一个结论,HQL 查询是没有原生 SQL 查询快的。
Hibernate 提供了 Query 组件执行 HQL 语句。
Query query=session.createQuery(hql);
Query 与原生 JDBC 中的 Statement 组件很相似,但其功能更高级、强大。
调用 Query 对象中 list() 查询方法,就能查询出开发者所需要的数据。
List<Student> stus= query.list();
最后,享受数据的时刻:
for (Student student : stus) {
System.out.println(student);
}
完整的实例:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
String hql="from Student s";
Query query=session.createQuery(hql);
List<Student> stus= query.list();
for (Student student : stus) {
System.out.println(student);
}
return null;
}
});
执行实例,在控制台可看到输出了所有学生信息。简直是简单得不要不要。
如果只想查询某几个属性的值,又该如何查询呢?
很简单,重构一下 HQL 语句:
String hql="select s.stuId,s.stuName from Student s";
此时查询出来的数据不是一个完整的对象。list() 方法查询出来的结果不能直接封装到 Student 类型中。
Hibernate 会把查询出来的每一行数据封装到一个数组中,所以 List 应该 是一个数组的集合,完整实例代码如下:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
String hql="select s.stuId,s.stuName from Student s";
Query query=session.createQuery(hql);
List<Object[]> stus= query.list();
for (Object[] student : stus) {
System.out.println(student[0]+":"+student[1]);
}
return null;
}
});
控制台输出结果:
Hibernate:
select
student0_.stuId as col_0_0_,
student0_.stuName as col_1_0_
from
Student student0_
1:Hibernate
2:Session
3:SessionFactory
24:Configuration
如果一定要使用 Student 类型接收查询数据则必须保证 Student 类中存在如下的构造方法:
public Student(Integer stuId, String stuName) {
this.stuId = stuId;
this.stuName = stuName;
}
然后修改 HQL 语句:
String hql="select new Student(s.stuId,s.stuName) from Student s";
Query query=session.createQuery(hql);
List<Student> stus= query.list();
for (Student student : stus) {
System.out.println(student);
}
建议大家使用上面的方案,毕竟很 OOP 嘛。其实也可以使用 Map 封装查询出来的数据。
String hql="select new Map(s.stuId as sid,s.stuName as sname) from Student s";
Query query=session.createQuery(hql);
List<Map<String,Object>> stus= query.list();
for (Map<String,Object> map : stus) {
System.out.println(map.get("sid")+":"+map.get("sname"));
}
道路千万条,选择在你手上。
2.2 HQL 高级查询
强参数查询
使用 SQL 查询时,可以指定查询条件,这个地球人都知道。HQL 中同样能使用条件查询:
from Student s where s.stuId> 2
在 HQL 中,如果查询条件中的数据需要通过参数传递,则会有两种方案:
- 匿名方案,已经司空见惯,对不对;
from Student s where s.stuId> ?
- 命名参数方案。
from Student s where s.stuId> :id
参数名前面一定要有一个冒号 :id。
完整实例献上:
String hql="from Student s where s.stuId> :id";
Query query=session.createQuery(hql);
query.setInteger("id", 2);
List<Student> stus= query.list();
for (Student student : stus) {
ystem.out.println(student);
}
return null;
可自行查看控制台上的输出结果。强命名参数和 ? 占位符作用是一样的,但是,强命名参数可减少指定实参时的出错率。
分页查询
分页查询是很实用的查询机制。
使用原生 SQL 分页查询时,需要自己构建查询 SQL 语句,不同的数据库中的分页查询语句编写也有差异性。Hibernate 通过其提供的分页查询功能很好地避开了这些问题。
分页查询之前,先搞清楚几个与查询有关的参数:
- pageSize: 每一页大小;
- pageNum: 页码。
假如数据库中有 20 行数据,分页查询时指定 pageSize 为 5,则每 5 条数据为一个逻辑页,总共有 4 页。
如果要查询第 3 页数据,即 pageNum=3。
则需要跳过去的记录数为:(pageNum-1)*pageSize=(3-1)*5=10 ,也就是从第 11 条数据开始查询。
现在直接上实例代码:
String hql = "from Student s order by stuId" ;
Query query = session.createQuery(hql);
int pageNum=3;
int pageSize=5;
int passNum=(pageNum-1)*pageSize;
query.setFirstResult(passNum);
query.setMaxResults(pageSize);
List<Student> stus = query.list();
for (Student student : stus) {
System.out.println(student.getStuName());
}
return null;
HIbernate 会从第 11 条记录开始,查询出 5 条记录。针对不同的数据库系统,Hibernate 会给出最佳的 SQL 分页方案。
联合查询
程序中所需要的数据可不一定在同一张表中,往往都是在多张表中。原生 SQL 通过多表连接或子查询方式解决这个问题。
使用 HQL 一样能表达出多表连接的意图。
可能你会问:
前面的一对一、一对多、多对多映射关联关系后,不就已经能够查询出多张表中的数据吗。
如下面表数据:
在学生类中采用立即查询策略:
@ManyToOne(targetEntity = ClassRoom.class, cascade = CascadeType.REMOVE,fetch=FetchType.EAGER)
@JoinColumn(name = "classRoomId")
public ClassRoom getClassRoom() {
return classRoom;
}
查询所有学生:
String hql = "from Student s";
Query query = session.createQuery(hql);
List<Student> stus = query.list();
System.out.println("-----------------------------");
for (Student student : stus) {
System.out.println("学生姓名:"+student.getStuName());
System.out.println("班级名称: "+student.getClassRoom().getClassRoomName());
}
return null;
不要怀疑,结果一定是会出现的。但是,可以看到控制台输出了很多 SQL 语句。
那是因为,Hibernate 会先查询出所有学生,然后根据班级 ID 再进入班级表进行查询,这就是 Hibernate 查询过程的 1+N 问题。
可改成下面的关联查询方式:
String hql = "select s.stuName,c.classRoomName from Student s,ClassRoom c where s.classRoom=c";
Query query = session.createQuery(hql);
List<Object[]> stus = query.list();
System.out.println("-----------------------------");
for (Object[] student : stus) {
System.out.println("学生姓名:"+student[0]);
System.out.println("班级名称: "+student[1]);
}
return null;
控制台输入结果:
Hibernate:
select
student0_.stuName as col_0_0_,
classroom1_.classRoomName as col_1_0_
from
Student student0_ cross
join
ClassRoom classroom1_
where
student0_.classRoomId=classroom1_.classRoomId
Hibernate 仅构建了一条 SQL 语句,直接查询出来了所有数据,看得出来,其性能要大于 1+N 方案。
HQL 比想象中要简单,比你预期的功能要强大。有了它,再也不怕查询不到我们需要的数据。
2.3 HQL 与函数
先把上一节课程中遗留的内容向大家介绍一下。
使用原生 SQL 查询时,可以在查询语句中嵌入聚合函数,HQL 查询中也可以使用聚合函数。
数据的用途之一是用于逻辑,产生新的数据。另一个用途是可产生报表,用于决策。
聚合函数的作用在于数据统计和分析,其现实意义很大。
HQL 中能使用哪些聚合函数?
答案是:SQL 中的聚合函数全部可照搬过来。
实例:统计学生相关信息。
String hql = "select count(*),sum(s.stuId),avg(s.stuId),max(s.stuId),min(s.stuId) from Student s";
Query query = session.createQuery(hql);
Object[] stus = (Object[]) query.uniqueResult();
System.out.println("学生总人数:" + stus[0]);
System.out.println("学生编号求和:" + stus[1]);
System.out.println("学生编号平均值:" + stus[2]);
System.out.println("学生编号最大值:" + stus[3]);
System.out.println("学生编号最小值:" + stus[4]);
使用过程很简单,上面实例中用到了 Query 对象的 uniqueResult() 方法,当确定查询结果只有一行记录时,可以使用此方法。
好了,有句话说得好,师傅引进门,学艺在个人。因为 HQL 是对 SQL 的 OOP 封装,其内涵是一样。对于很熟悉 SQL 语法的你们来讲,全完掌握 HQL 也只时间的问题,不会存在技术上的难点。
3. 小结
又到了要总结的时候,本课程给大家介绍了 HQL 查询语法,还有更多细节留待后面慢慢研究。
HQL 语法是一种类似于 SQL 语法,形式上与 SQL 一样,但本质有很大区别。HQL 是面向对象的查询语法结构。注意语句中哪些地方不区分大小,哪些地方 区分大小写。
本节课也聊到了 HQL 的关联查询语法,很好解决了 1+N 问题。