HQL语句是hibernate中的一个重要知识,今天我们来看看HQL语句吧,下面有三个类图,我们就以这三个类为例讲解HQL语句吧。
既然我们现在已经学习了两种配置hibernate的方式(XML和annotation),下面我们就使用这两种方式来配置出两个项目来讲解HQL(hibernate中的XML和annotation配置后的运行结果不一样的,这个我们前面就说过了)。我们使用双向关联来实现:
HQL(hibernate query language)的基本使用
Hibernate中,我们前面实现了一个基本的CURD操作的,当时我们发现一些基本的增删改查hibernate已经帮助我们完成了,那么如果我们遇到一些比较复杂的查询时应该如果使用呢(如多表关联查询)?这个我们就需要使用hibernate的HQL语句来完成查询了。
基本的HQL查询
下面我们来做一个简单的列表查询吧:
@Test publicvoid test01() { Session session = null; try { session = HibernateUtil.openSession(); //注意:是基于对象的查询,不是基于数据库表的查询--> Special Query query = session.createQuery("from Special"); List<Special> specials = query.list(); for (Special special : specials) { System.out.println(special.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
运行结果如下:
我们发现hibernate的hql语句是基于对象的查询,注意:from后面的是类名,不是表名。那么我们还能使用select * from 表名这种查询吗?
@Test publicvoid test02() { Session session = null; try { session = HibernateUtil.openSession(); Query query = session.createQuery("select * from Special"); List<Special> specials = query.list(); for (Special special : specials) { System.out.println(special.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
我们发现,HQL语句无法使用select *这种sql语句,那么如果我们想要实现类似的效果怎么办呢?
@Test publicvoid test02() { Session session = null; try { session = HibernateUtil.openSession(); //HQL语句不能使用select * 这种查询 //Query query = session.createQuery("select * from Special"); //我们可以使用基于别名的查询,使用别名来实现sql类似于 //select *的这种查询 //也可以使用这种链式结构来查询 List<Special> specials = session. createQuery("select s from Special as s").list(); for (Special special : specials) { System.out.println(special.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
在真正的项目中,我们肯定会遇到带条件的查询的,那么HQL如何使用条件查询呢?
@Test publicvoid test03() { Session session = null; try { session = HibernateUtil.openSession(); List<Student> students = session. createQuery("from Student where name like '%刘%' ").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
这种查询我们在JDBC的时候就说过,当sql语句需要拼接的话,会存在sql注入漏洞问题,所以我们一般都是用?来为参数赋值。
@Test publicvoid test04() { Session session = null; try { session = HibernateUtil.openSession(); //也可以使用?问号的方式来为条件参数赋值 //hibernate的setParameter位置赋值是从0开始的 //JDBC是从1开始的 List<Student> students = session. createQuery("from Student where name like ? ") .setParameter(0, "%刘%").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
两种方式运行结果一致:
在setParemeter的设置值的方式有基于?问号占位符的数字(位置)来查询,也可以基于别名来查询,下面我们来看看:
@Test publicvoid test05() { Session session = null; try { session = HibernateUtil.openSession(); //当我们不想使用位置来完成赋值的时候 //还可以使用别名来完成赋值,就是在属性字段后加:别名 //在赋值的时候使用别名来完成赋值 List<Student> students = session. createQuery("from Student where name like :name and sex=:sex "). setParameter("name", "%林%").setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
可以看出这种方式也能够完成查询。
常用的HQL查询
如果我们查询数据的数量呢?以前我们使用的是select count(*)来完成查询,hibernate中如果使用呢?
@Test publicvoid test06() { Session session = null; try { session = HibernateUtil.openSession(); //查询数量时可以使用count(*)来完成查询 //注意:使用了查询数量的返回值是一个Long类型 List<Long> count = session. createQuery("select count(*) from Student where sex=:sex "). setParameter("sex", "男").list(); System.out.println(count.get(0)); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
运行结果如下:
我们发现这次的结果是一个唯一的值,只有一个数字,使用List接受感觉不太方便,那么当以后我们再遇到这种查询结果只有一个值的时候,我们可以不使用list方法,使用uniqueResult方法表示查询结果只有一个结果集。
@Test publicvoid test06() { Session session = null; try { session = HibernateUtil.openSession(); //uniqueResult表示查询结果为一个结果集时使用 Long count = (Long) session. createQuery("select count(*) from Student where sex=:sex "). setParameter("sex", "男").uniqueResult(); System.out.println(count); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
注意:uniqueResult返回一个对象,需要强制类型转换。
如我们查询一个结果:
@Test publicvoid test07() { Session session = null; try { session = HibernateUtil.openSession(); //uniqueResult表示查询结果为一个结果集时使用 Student stu1 = (Student) session .createQuery("from Student where id=?") .setParameter(0, 10).uniqueResult(); System.out.println(stu1.getName()); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
如果我们要查询的不是对象的属性字段呢?如计数统计。这样的话,我们就没办法使用具体的对象来接受查询结果了,所以hibernate提供了通过对象数组的方式让我们来接受一些不能使用对象接受的结果集,如下面统计男女各多少人的时候:
@Test publicvoid test08() { Session session = null; try { session = HibernateUtil.openSession(); //投影查询时(就是无法使用具体的对象来封装数据的查询) //需要使用Object的数组来接受数据 List<Object[]> list = session.createQuery("select s.sex,count(s.sex) from Student s group by s.sex").list(); for (Object[] o : list) { System.out.println(o[0]+":"+o[1]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
使用对象数组的确可以接受,但是这种方式我们感觉不太习惯,所以hibernate还提供了另一种方式,就是我们写一个vo对象(vo针对的是po对象,po是我们的实体类对象,vo就是一个标准的Java类,但是不存在数据库的关系,仅仅作为封装和传递数据的对象存在)。如下面所写:
@Test publicvoid test09() { Session session = null; try { session = HibernateUtil.openSession(); /** * 我们可以使用vo对象来完成对于映射的查询,当然有时候 * 我们也会叫它为Dto(数据传输对象) * 注意:使用的是new关键字,调用的使用vo的构造方法 * 所以一定要有这个构造方法才行 */ List<StuCount> list = session.createQuery( "select new com.lzcc.hibernate.entity.StuCount(s.sex as sex ,count(s.sex) as count) from Student s group by s.sex").list(); for (StuCount o : list) { System.out.println(o.getSex()+","+o.getCount()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
需要一个vo(Dto)对象类:
package com.lzcc.hibernate.entity; /** * 注意:这个类没有其他任何含义 * 仅仅是作为数据的传输存在的 */ publicclass StuCount {
private String sex; privatelongcount;
public String getSex() { returnsex; }
publicvoid setSex(String sex) { this.sex = sex; }
publiclong getCount() { returncount; }
publicvoid setCount(long count) { this.count = count; }
public StuCount() { }
public StuCount(String sex, long count) { this.sex = sex; this.count = count; } } |
运行效果一致:
我们的查询还可以基于导航的查询,如在Student中存在Classroom的属性字段,那么我们就可以通过Classroom的id来完成导航查询,如查询id为1的班级的所以学生:
@Test publicvoid test10() { Session session = null; try { session = HibernateUtil.openSession(); //基于导航的查询 List<Student> students = session. createQuery("from Student where classroom.id = ?"). setParameter(0,1).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
这样使用起来就方便多了。但是上面的查询本身在一个表中,所以查询是只会发送一条sql,那么如果我们关联查询多张表呢?
@Test publicvoid test11() { Session session = null; try { session = HibernateUtil.openSession(); //基于导航的查询 List<Student> students = session. createQuery("select s from Student s where s.classroom.name = ? and s.sex = :sex"). setParameter(0,"好汉班").setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
这种查询我们发现也是一条sql就完成了两个表的关联查询,但是我们注意到,hibernate使用了cross join来完成关联的,注意这是一种基于笛卡尔积的查询,效率不高,如果考虑效率问题的话,不建议使用,当然要使用问题也不大,很方便。
下面我们来查询一班和二班的所有男学生呢?
@Test publicvoid test12() { Session session = null; try { session = HibernateUtil.openSession(); //基于导航的查询 List<Student> students = session. createQuery("select s from Student s where s.sex=:sex and (s.classroom.id = ? or s.classroom.id = :id)") .setParameter(0,1).setParameter("sex", "男").setParameter("id", 2).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
如果我们这样写的话,我们发现代码报错了,这是因为位置的参数赋值必须在别名的参数前面。
@Test publicvoid test12() { Session session = null; try { session = HibernateUtil.openSession(); //基于导航的查询 List<Student> students = session. createQuery("select s from Student s where (s.classroom.id = ? or s.classroom.id = :id) and s.sex=:sex ") .setParameter(0,1).setParameter("sex", "男").setParameter("id", 2).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
这样写就没有问题了
当然我们也可以使用sql中的in这个关键字:
@Test publicvoid test13() { Session session = null; try { session = HibernateUtil.openSession(); //也可以使用sql中in这个关键字 List<Student> students = session. createQuery("select s from Student s where s.classroom.id in (?,?) and s.sex=:sex ") .setParameter(0,1).setParameter(1, 2).setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
同样我们发现这两个id参数是一个列表,这样的话,hibernate也提供了一个方便与列表赋值的方式:
@Test publicvoid test14() { Session session = null; try { session = HibernateUtil.openSession(); //我们可以使用setParameterList完成列表的赋值,注意这个只能使用别名 List<Student> students = session. createQuery("select s from Student s where s.classroom.id in (:clas) and s.sex=:sex ") .setParameterList("clas", new Integer[]{1,2}) .setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
这两次的代码运行效果如下:
下面我们来看看分页查询吧:
@Test publicvoid test15() { Session session = null; try { session = HibernateUtil.openSession(); //使用setFirstResult设置分页的开始点((当前页-1)*每页条数) //使用setMaxResult设置每页显示的条数 List<Student> students = session. createQuery("select s from Student s where s.classroom.id in (:clas)") .setParameterList("clas", new Integer[]{1,2}) .setFirstResult(0).setMaxResults(5).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
下面再看看其他的一些查询,如为空查询等:
@Test publicvoid test16() { Session session = null; try { session = HibernateUtil.openSession(); //可以使用 is null 或者 =null来设置为空的条件 /* List<Student> students = session. createQuery("select s from Student s where s.classroom.id = null") .list();*/ List<Student> students = session. createQuery("select s from Student s where s.classroom.id is null") .list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
HQL的关联查询
有时候我们可能存在多表的关联查询,这样的话,我们前面的查询可以使用前面的导航查询,但是导航查询有时候效率不高,所以下面给大家介绍另一种查询,就是连接查询(join)。
如果我们要查询id为1的班级的所有学生:
@Test publicvoid test17() { Session session = null; try { session = HibernateUtil.openSession(); /** * 使用join完成关联查询, * 注意:必须是存在关联关系的表之间才可以查询 * 因为我们本质上使用了一个对象,另一个表是引 * 用这个对象的属性映射过去的,同时也注意一个问题 * 就是必须使用select 别名这个,不能省略,因为 * 当两张表都存在后,如果我们不指定需要查询的字段或者表 * 则hibernate不知道我们要查询的字段,就会报错 */ List<Student> students = session. createQuery("select from Student s join s.classroom c where c.id=1") .list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
我们发现代码这样也可以完成查询的,使用的是inner join,这样就比导航的cross join的效率高了,这是内连接查询。
同样我们的数据库连接除了内连接外,还有外连接,包括左外连接和右外连接,左外连接如果右面的没有关联左面的,则不显示右面没有关联的部分。右外连接是如果左面的没有关联,则不显示左面没有关联的部分。如下面我们查询所有班级的人数:
@Test publicvoid test18() { Session session = null; try { session = HibernateUtil.openSession(); List<Object[]> students = session. createQuery("select c.name,count(s.classroom.id) from Student s right join s.classroom c group by c.id ") .list(); for (Object[] student : students) { System.out.println(student[0]+":"+student[1]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
这样我们就完成了右外连接的查询,左外连接于此相同。
有时候我们会使用多个表中的字段完成查询,如我们在报表时,我们一般要求有学生的专业名称,班级名称,学习一些其他属性(如名字,性别等),那么下面我们来完成这个查询吧。
@Test publicvoid test19() { Session session = null; try { session = HibernateUtil.openSession(); List<Object[]> students = session. createQuery("select s.name,s.sex,c.name,spe.name from Student s left join s.classroom c left join c.special spe ") .list(); for (Object[] student : students) { System.out.println(student[0]+"--"+student[1]+"--"+student[2]+"--"+student[3]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
代码运行效果如下:
这里我们也可以使用我们前面讲过的Dto(vo)来实现效果,因为在真实的项目开发中,我们在dao层完成数据的封装,在页面显示,如果我们使用了对象数组,这样我们不便于知道到底有哪些字段需要显示,而使用了对象的话,就很清楚了
我们先写个Dto对象:
package com.lzcc.hibernate.dto;
publicclass StudentDto{
privateintid; private String name; private String sex; private String className; private String specialName;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public String getSex() { returnsex; }
publicvoid setSex(String sex) { this.sex = sex; }
public String getClassName() { returnclassName; }
publicvoid setClassName(String className) { this.className = className; }
public String getSpecialName() { returnspecialName; }
publicvoid setSpecialName(String specialName) { this.specialName = specialName; }
public StudentDto() { }
public StudentDto(int id, String name, String sex, String className, String specialName) { super(); this.id = id; this.name = name; this.sex = sex; this.className = className; this.specialName = specialName; } } |
测试代码如下,注意写法,必须是去路径(包路径+类名),这个构造函数必须有,别名也必须和属性名称保持一致。
@Test publicvoid test20() { Session session = null; try { session = HibernateUtil.openSession(); /** * 使用vo对象完成对数据的封装 */ List<StudentDto> students = session. createQuery("select new com.lzcc.hibernate.dto.StudentDto(s.id as id, s.name as name, s.sex as sex, c.name as className, spe.name as specialName) from Student s left join s.classroom c left join c.special spe ") .list(); for (StudentDto student : students) { System.out.println(student.getId()+"***"+student.getName()+"***"+student.getSex() +"***"+student.getClassName()+student.getSpecialName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
下面我们来看看其他一些sql中的使用,如having等
我们要去查出专业人数大于10的专业名称:
@Test publicvoid test22() { Session session = null; try { session = HibernateUtil.openSession(); List<Object[]> students = session. createQuery("select spe.name,count(s.classroom.special)" + " from Student s right join s.classroom.special spe" + " group by spe having count(s.classroom.special) > 10") .list(); for (Object[] student : students) { System.out.println(student[0]+":"+student[1]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
Having在group by之后,作为分组的筛选条件存在的。
这样就完成了多表之间的联合查询了。
我们现在将测试文件复制到基于annotation配置的项目中,发现的确也一样能够运行成功。说明HQL在两种配置中是一致的。但是注意了:基于annotation配置的如果没有延迟加载配置的话,可能导航查询本身一条sql完成查询,hibernate会发送好几条完成,这是因为没有延迟加载,hibernate不会自动抓取关联数据,但是XML会自动抓取,但是使用的是cross join,这个我们前面说过了,建议大家使用join,而不建议使用导航的查询,当然这个问题需要结合项目的实际情况来考虑。