Hibernate 查询语言(HQL)

1. 前言

本节课程和大家一起学习 Hibernate 中的 HQL ( Hibernate 查询语言)。通过本节课程的学习,你将了解到:

  • HQL 基础语法;
  • HQL 查询的具体实现。-

2. HQL

查询?前面不是讲过?用过吗?

但是,前面的查询都是简单查询,真实项目中的查询需求要远比这个复杂。仅仅依靠 get()、load() 是远远达不到要求。

Hibernate 提供了灵活多样的查询机制,几乎能做到无死角查询。

  1. 标准化对象查询 (Criteria Query): 以对象的方式进行查询,将查询语句封装为对象操作。
  • 优点: 可读性好,符合 Java 程序员的编码习惯。
  • 缺点: 不够成熟,不支持投影(projection)或统计函数(aggregation)
  1. Hibernate 语言查询(Hibernate Query Language,HQL): 它是完全面向对象的查询语句,查询功能非常强大,具有继承、多态和关联等特性 。Hibernate 官方推荐使用 HQL 进行查询。

  2. 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 行数据,分页查询时指定 pageSize5,则每 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 是对 SQLOOP 封装,其内涵是一样。对于很熟悉 SQL 语法的你们来讲,全完掌握 HQL 也只时间的问题,不会存在技术上的难点。

3. 小结

又到了要总结的时候,本课程给大家介绍了 HQL 查询语法,还有更多细节留待后面慢慢研究。

HQL 语法是一种类似于 SQL 语法,形式上与 SQL 一样,但本质有很大区别。HQL 是面向对象的查询语法结构。注意语句中哪些地方不区分大小,哪些地方 区分大小写。

本节课也聊到了 HQL 的关联查询语法,很好解决了 1+N 问题。