HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似。在Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。它具有以下功能:
- 在查询语句中设定各种查询条件。
- 支持投影查询,即仅检索出对象的部分属性。
- 支持分页查询。
- 支持分组查询,允许使用group by和having关键字。
- 提供内置聚集函数,如sum()、min()和max()。
- 能够调用用户定义的SQL函数。
- 支持子查询,即嵌套查询。
- 支持动态绑定参数。
Session 类的 Qurey 接口支持HQL检索方式,它提供了以上列出的各种查询功能。
注:Qurey 接口支持方法链编程风格,它的set方法都返回自身实例,而不是返回void类型。方法链编程风格能使程序代码更加简洁。
一、HQL 查询的步骤
写 HQL 语句注意: 表名与列名 对应 持久化类的类名和字段名,严格区分大小写,HQL不支持 * 号,一般使用别名 ,
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 写HQL
String hql = "from Activity"; //等价于: String hql = "select a from Activity a";
//2. 获取Query<>
Query<Activity> query = session.createQuery(hql);
//3. 给动态绑定参数赋值
//4. 获取list(查询) 增删改 使用executeUpdate()
List<Activity> list = query.list();
for (Activity activity : list) {
System.out.println(activity.getAname() +"--"+ activity.getMember().size() );
}
}
二. HQL 动态绑定参数查询
Activity 与 Member 多对多
1). 使用 ? 做占位符: Hibernate setParameter() 占位符参数从 0 开始
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 写HQL
String hql = "select a from Activity a where a.id<? and a.aname like ?";
//2. 获取Query<>
Query<Activity> query = session.createQuery(hql);
//3. 给动态绑定参数赋值 Hibernate 从0开始
query.setParameter(0, 5);
query.setParameter(1, "建%");
//4. 获取list
List<Activity> list = query.list();
for (Activity activity : list) {
System.out.println(activity.getAname() +"--"+ activity.getMember().size() );
}
}
--------
建军节--2
Hibernate:
select
member0_.activity_id as activity1_1_0_,
member0_.member_id as member_i2_1_0_,
member1_.id as id1_2_1_,
member1_.m_name as m_name2_2_1_
from
t_activity_member member0_
inner join
t_member member1_
on member0_.member_id=member1_.id
where
member0_.activity_id=?
建党节--1
2). 使用 :自定义名 做占位符: Hibernate setParameter() 占位符参数 用字符串表示, 推荐使用 可读性强
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 写HQL
String hql = "select a from Activity a where a.id<:aid and a.aname like :an";
//2. 获取Query<>
Query<Activity> query = session.createQuery(hql);
//3. 给动态绑定参数赋值
query.setParameter("aid", 5);
query.setParameter("an", "建%");
//4. 获取list
List<Activity> list = query.list();
for (Activity activity : list) {
System.out.println(activity.getAname() +"--"+ activity.getMember().size() );
}
}
3. where 筛选参数可以是基本数据类型外,还可以是一个对象(必须有id,否则报错)
Teacher 与 Student 一对多
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 写HQL
String hql = "select s from Student s where s.teacher = ?";
//2. 获取Query<>
Query<Student> query = session.createQuery(hql);
//3. 给动态绑定对象参数赋值
Teacher teacher = new Teacher();
teacher.setId(3);
query.setParameter(0, teacher);
//4. 获取list
List<Student> list = query.list();
for (Student Student : list) {
System.out.println(Student.getSname() +"--"+ Student.getTeacher().getTname());
}
}
--------
学生3--老师3
学生4--老师3
三、HQL 分页查询: mysql 和 orcale 都适合。
分页查询主要靠两个方法来实现:
setFirstResult(index) : 从index这个索引值开始查询数据库的记录, index 从 0 开始
setMaxResults(数量) : 表示从起始的索引对应的记录开始, 最多查询出的记录数目
@SuppressWarnings("unchecked")
@Test
public void test() {
int pageNo = 4;
int pageSize = 5;
String hql = "from Student where id>?"; //可以加条件
Query<Student> query = session.createQuery(hql);
query.setParameter(0, 5);
//使用下 链式语法
query.setFirstResult((pageNo-1)*pageSize).setMaxResults(pageSize);
List<Student> list = query.list();
//System.out.println(Arrays.toString(list.toArray()));
for (Student Student : list) {
System.out.println(Student.getSname());
}
}
四、只查询出表中的部分字段
只查询出表中的部分字段, 在 Hibernate 中叫做投影查询:
1. 方法一:多字段(大于1)查询
@Test
public void test() {
String hql = "select s.sname,s.teacher from Student s";
//返回结果为 Object[]的List集合
List<Object[]> list = session.createQuery(hql).list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
}
--------
[学生1, cn.jq.hibernate5.model.Teacher@5b6e8f77]
[学生2, cn.jq.hibernate5.model.Teacher@5b6e8f77]
[学生3, cn.jq.hibernate5.model.Teacher@6b7d1df8]
[学生4, cn.jq.hibernate5.model.Teacher@6b7d1df8]
[学生5, cn.jq.hibernate5.model.Teacher@3044e9c7]
[学生6, cn.jq.hibernate5.model.Teacher@41d7b27f]
。。。
方法一投影查询返回结果是一个 List<Object[]> 类型,这是因为查询的字段是多个(大于1),所以是 Object[] 数组,如果我们业务层很多方法都是以对象来传递的,Object 类型要强制类型转换,不好!
2.方法二: 对象查询
@Test
public void test() {
String hql = "select new Student(s.sname) from Student s";
// 返回结果是 对用POJO类的List集合, 注意:必须要有对应参数的构造方法
List<Student> list = session.createQuery(hql).list();
for (Student student : list) {
System.out.println(student);
}
}
----其他值为空或默认基本类型值--
Student [id=0, sname=学生1, teacher=null]
Student [id=0, sname=学生2, teacher=null]
Student [id=0, sname=学生3, teacher=null]
Student [id=0, sname=学生4, teacher=null]
。。。
方法二返回结果是一个 List<POJO类> 对象集合类型,(被泛型限定的POJO类),这种方式解决了传递 Object[] 方式,只传递对象了,且也实现了,只查询部分属性。
注意: POJO 类映射对象中必须要有对应参数查询语句的构造方法
五. HQL语句中使用聚合函数
重点内容
函数名 | 说明 |
count() | 统计选择对象的记录条数 |
sum() | 求和 |
max() | 求最大值 |
min() | 求最小值 |
avg() | 计算选择属性的平均值 |
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "select sum(s.id),avg(s.id),max(s.id),min(s.id),count(s.id),s.teacher from Student s group by s.teacher.id having s.teacher.id < ?";
//返回结果为 Object[]的List集合
List<Object[]> list = session.createQuery(hql).setParameter(0, 6).list();
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
}
--------
[3, 1.5, 2, 1, 2, Teacher [id=1, tname=老师1]]
[7, 3.5, 4, 3, 2, Teacher [id=3, tname=老师3]]
[18, 6.0, 7, 5, 3, Teacher [id=4, tname=老师4]]
结论: HQL 语言和 SQL 语言基本上是相通的,只是 将字段,表对应成了属性和类名字 即可。
六. HQL连接查询
HQL提供的连接方式:
迫切连接是指在指定连接方式时不仅指定了连接查询方式,而且显式地指定了关联级别的查询策略。
Hibernate 使用 fetch 关键字实现,fetch 关键字表明“左边”对象用于与“右边”对象关联的属性会立即被初始化。
平时项目中都建议使用 fetch !
多对一关联,无迫切连接和迫切连接比较(其他连接类同):
sql 中 表名 inner join 表名
hql 中 POJO类名 inner join 关联 属性类
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "from Student s inner join s.teacher t where s.id<? and t.id<?";
//返回结果为 Object[]的List集合
List<Object[]> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
}
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "from Student s inner join fetch s.teacher t where s.id<? and t.id<?";
//返回结果为 POJO类的List集合
List<Student> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Student student : list) {
System.out.println(student);
}
}
--------
Student [id=1, sname=学生1, teacher=Teacher [id=1, tname=老师1]]
Student [id=2, sname=学生2, teacher=Teacher [id=1, tname=老师1]]
Student [id=3, sname=学生3, teacher=Teacher [id=3, tname=老师3]]
Student [id=4, sname=学生4, teacher=Teacher [id=3, tname=老师3]]
Student [id=5, sname=学生5, teacher=Teacher [id=4, tname=老师4]]
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "select s from Student s inner join s.teacher t where s.id<? and t.id<?";
//返回结果为 POJO类的List集合, 多条sql语句
List<Student> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Student student : list) {
System.out.println(student);
}
}
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "select s from Student s inner join fetch s.teacher t where s.id<? and t.id<?";
//返回结果为 POJO类的List集合, 一条sql语句
List<Student> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Student student : list) {
System.out.println(student);
}
}
--------
Hibernate:
select
student0_.id as id1_3_0_,
teacher1_.id as id1_4_1_,
student0_.sname as sname2_3_0_,
student0_.teacher_id as teacher_3_3_0_,
teacher1_.tname as tname2_4_1_
from
t_student student0_
inner join
t_teacher teacher1_
on student0_.teacher_id=teacher1_.id
where
student0_.id<?
and teacher1_.id<?
使用结论:
1. 使用 from.... 没加 fetch 和加了,得到的结果类型是不一样的,只发起一条 sql .
2. 使用 select ... 指定查询对象时, 加 fetch, 只发起一条 sql , 反之发起多条 sql , 结果类型是一样。
根据发起的 sql 条数越来效率越高的原则,项目开发中, 推荐都加fetch
fetch关键字只对 inner join 和 left join 有效。right join 基本也不用.
七. HQL批量增删改
每个 Hibernate Session 中都维持了一个必选的数据缓存,这个内部缓存正常情况下是由 Hibernate 自动维护的,并且没有容量限制。在批量插入与更新时,由于每次保存的实体都会保存在Session缓存中,当数据量大的时候,就可能出现OutOfMemoryException(内存溢出异常)。所以批量增加或更新操作中, 应该考虑到控制内部缓存的过度增长而出现OutOfMemeoryError 错误。
这里有两个方面进行控制:
一. 是在数据保存过程中周期性的对 Session 调用 flush 和 clear 方法,确保 Session 的容量不至于太大。
二. 是设置 hibernate.jdbc.batch_size 参数来指定每次提交SQL的数量。
setParameter() setParameterList() executeUpdate()
1. 批量插入(不推荐):推荐使用 save()
@Test
public void test() {
List<Student> list = new ArrayList<Student>();
for(int i=50; i < 85; i++) {
Student student = new Student();
student.setSname("学生" + i);
list.add(student);
}
int row = 10; //
for(int i =0; i<list.size(); i++) {
session.save(list.get(i));
if(i % row == 0) {
session.flush();
session.clear();
}
}
}
Session.flush():1.刷新所有数据;2.执行数据库SQL完成持久化动作;必须在操作结束且在提交事务和关闭连接之前被调用。
Session.clear(): 则是清除 session 中的缓存数据。这样就达到控制session的一级缓存的大小
2. 批量修改
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "update Student s set s.sname = ? where s.id > ?";
int n = session.createQuery(hql).setParameter(0, "李斯").setParameter(1, 15).executeUpdate();
System.out.println(n);
}
3. 批量删除
@SuppressWarnings("unchecked")
@Test
public void test() {
Integer[] ids = {16,19,10}; //注意Object[]类型
String hql = "delete from Student s where s.id in (:ids)";
int n = session.createQuery(hql).setParameterList("ids", ids).executeUpdate();
System.out.println(n); }
-------
Hibernate:
delete
from
t_student
where
id in (
? , ? , ?
)
3
批量操作总结:
1)优化 HIbernate API , 程序上采取分段插入或更新时 及时调用清除缓存方法。
2)通过 session.doWork()直接操作原生的 JDBC API 来做批量操作,性能更好。它具有以下优点:
(1) 不会消耗大量内存。因为它不再将数据库中的大批量数据先加载到内存中,然后再逐个更新或修改。
(2) 可以在一条SQL语句中更新或删除大批量的数据。