SQL和HQL只有一个字母只差,一个是结构化查询语言,一个是Hibernate查询语言,HQL延用了SQL中的语法、关键词。
HQL是Hibernate Query Language的简称,也就是说HQL是一种Hibernate查询语言,所以它在Hibernate世界里应用很方便。HQL是完全面向对象的查询语句,查询功能非常强大,具备多态、关联等特性。链接一篇关于HQL详细介绍的文章《HQL: Hibernate查询语言》,里面讲的内容很详细!
HQL简单介绍
HQL查询依赖于Query类,每个Query实例对应一个查询对象。使用HQL查询可按如下步骤进行:
1)获取Hibernate Session对象;
2)编写HQL语句;
3)以HQL语句作为参数,调用Session的createQuery方法创建查询对象;
4)如果HQL语句包含参数,调用Query的setXxx方法为参数赋值;
5)调用Query对象的list等方法遍历查询结果。
进入HQL世界
一个ORM框架是建立在面向对象的基础上的。最好的例子是Hibernate如何提供类SQL查询。虽然HQL的语法类似于SQL,但实际上它的查询目标是对象。HQL拥有面向对象语言的所有的特性,这其中包括多态、继承和组合。这就相当于一个面向对象的SQL,为了提供更强大的功能,HQL还提供了很多的查询函数。这些函数可以被分为四类:
1. 投影函数
2. 约束函数
3. 聚合函数
4. 分组函数
使用HQL可以建立简单的查询,也可以建立更复杂的查询。在本文中并不讨论那些非常复杂的查询,如含有子查询和很多连接的查询。本文只讨论连接两个表的查询。现在让我们开始接近HQL吧!
投影
如谓投影,就是一个可以访问的对象或对象的属性。在HQL中,可以使用from和select子句来完成这个工作。
from子句返回指定的类的所有实例。如from Order将返回Order类的所有实例。换句话说,以上的查询相当于以下的SQL语句:
select * from order |
from 是最简单的查询子句。from后面可以跟一个或多个类名(类名也可以带有别名)。为了得到Order和Product的所有实例,可以使用如下的查询:
from Order, Product |
和类名一样,别名也可以在from后使用,如下代码如示:
from Order as o, Product p |
当查询很复杂时,加入别名可以减少语句的长度。我们可以看看如下的SQL语句:
select o.*, p.* from order o, product p where o.order_id = p.order_id |
我们可以很容易看出,上面的查询是一对多的关系。在HQL中相当于一个类中包含多个其它类的实例。因此,以上的SQL写成HQL就是:
from Order as o inner join o.products as product |
现在让我们考虑另外一个从表中得到指定属性的情况。这就是最常用的select子句。这在HQL中的工作方式和SQL中一样。而在HQL中,如果只是想得到类的属性的话,select语句是最后的选择。以上的SQL可以使用select子句改成如下的HQL语句:
select product from Order as o inner join o.products as product |
以上的HQL语句将返回Order中的所有Products实例。如果要得到对象的某一个属性,可以将HQL语句写成如下的形式:
select product.name from Order as o inner join o.products as product |
如果要得到多个对象的属性,可以将HQL语句写成如下形式:
select o.id, product.name from Order as o inner join o.products as product |
接下来,我们将进入下一个议题。假设我们需要根据某些条件得到数据。那么以上所述的HQL语句将无法满足需求。为了达到这一目的,我们就要用到下面将要讨论的约束子句。
约束
从以上可知,投影返回的是所有的数据。但在大多数时候我们并不需要这么多数据。这就需要对数据进行过滤。在HQL中过滤数据的子句和SQL一样,也是where。它的语法类似于SQL,通过where子句,可以对行进行过滤。我们可以看看下面的SQL语句:
select * from orders where id = ‘1234’ |
这条查询语句返回了id等于1234的所有的字段。和这条SQL对等的是下面的HQL语句:
select o from Order o where o.id=’1234’ |
从以上两条语句可以看出,它们的where子句非常相似。而它们唯一的不同是SQL操作的是记录,而HQL操作的是对象。在HQL中,除了where子句可以过滤数据外,having子句也可以做到这一点(关于having子句的详细内容我将在分组部分讨论)。投影和约束是两个基本的操作,这两个操作再加上聚合函数的话,那HQL将变得更加强大。下面我们就来讨论什么是聚合。
聚合
上述的查询都是将每一个记录(对象)当做一个单位,而如果使用聚合,可以将一类记录(对象)当做一个单位。然后再对每一类的记录(对象)进行一系列地操作,如对某一列取平均值、求和、统计行数等等。HQL支持以下的聚合函数:
1. avg(…), sum(…)
2. min(…), max(…)
3. count(*), count(…), count(distinct…), count(all…)
以上的聚合函数都返回数值类型。这些操作都可以在select子句中使用,如下所示:
select max(o.priceTotal) + max(p.price) from Order o join o.products p group by o.id |
以上的HQL语句返回了两个值的和:orders表中的priceTotal的最大值和products表中的price的最大值之和。我们还可以使用having子句对分组进行过滤。如我们想按id统计priceTotal小于1000的数量可按如下的HQL语句去实现:
select count(o) from Order o having o.priceTotal < 1000 group by o.id |
我们还可以将聚合函数和having子句一起使用。如我们要按products表的id统计price小于amount的平均数的产品数量,HQL语句如下:
select count(p) from Product p having p.price < avg(amount) group by p.id |
从上面的一系列的HQL语句可以看出,所有通过SQL实现的,都可以通过HQL来实现。
分组
在上一部分,已经涉及到了分组的概念。分组操作的是行的集合。它根据某一列(属性)对记录集进行分组。这一切是通过group子句实现的。如下的例子描述了group子句的一般用法。
select count(o) from Order o having o.priceTotal >= 1200 and o.priceTotal <= 3200 group by o.id |
HQL中的分组和SQL中的分组类似。总之,除了一些对SQL的特殊扩展外,其它所有的SQL功能都可以使用HQL描述。在接下来的部分,让我们举例说明如何在java中使用HQL。
在java中使用HQL
到现在为止,我们已经学习了HQL的基本用法。接下来我们举一个例子来说明如何在Java中使用HQL。下面的例子只给出了主要的部分,由于本文只是讨论HQL的用法,因此,关于Hibernate的一些设置和在main()函数中调用Hibernate的部分并未给出,读者可以参考相关的文当。现在让我们看看下面的例子。
下面是必须引用的包
import java.util.List; import org.hibernate.*; import org.hibernate.cfg.* import com.Order; |
下面是类的声明
public class MyOrder { … … } |
下面让我们来实现MyOrder类的构造函数
public class MyOrder { SessionFactory sf; public MyOrder() { Configuration cfg = new Configuration().addClass(Order.class); sf = cfg.buildSessionFactory(); } … … } |
下面的getOrder函数根据priceTotal的区间值返回Order对象。
public class MyOrder { …. …. public Order getOrder(String lower, String upper) { // 打开一个会话 Session sess = sf.openSession(); // HQL语句 String query = "select o from o " + "Order as o join o.products as p " + "where o.priceTotal > :priceTotalLower" + "and o.priceTotal< :priceTotalUpper"; Query q = sess.createQuery(query); // 将两个参数传入HQL中 q.setDouble("priceTotalLower", Double.parseDouble(lower)); q.setDouble("priceTotalUpper", Double.parseDouble(upper)); List list = q.list(); Order o=(Order)list.iterator.next(); return o; } … … } |
下面的main函数将测试MyOrder类
public class MyOrder { … … public static void main(String args[]) { Order o=MyOrder().getOrder(“100”, “300”); System.out.println(“id=”+ o.id); … … } } |
小结
上述的代码演示了如何在Java中使用HQL,但HQL还有两点需要注意一下:
1. HQL并不区分字母的大小写,但在HQL中的Java类和属性名必须和实际的类和属性名一致。如SELECT和select之间可以互换,但Order和order却代表不同的含义。
2. 如果HQL中引用的类未被导入,在HQL中必须引用具体的包。如本例中,如果com.Order未被导入,在HQL中必须将Order写成com.Order。
=========================================================================================================================
1、简单属性查询【重要】
*单一属性查询,返会属性结果集列表,元素类型和实体类中相应的类型一致
*多个属性查询,多个属性查询返会对象数组,对象数组的长度取决于属性的个数,对象数组中元素的类型取决于属性在实体类中的类型
*如果认为返会数组不够对象化,可以使用hql动态实例化Student对象
单一属性查询
- //返回结果集属性列表,元素类型和实体类中的属性类型一致
- Liststudents = session.createQuery("select name from Student").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Stringname = (String)iter.next();
- System.out.println(name);
- }
多个属性查询
- //查询多个属性,返回对象数组集合
- //数组元素的类型与查询的属性类型一致
- //数组的长度与select中查询的属性个数一致
- Liststudents = session.createQuery("select id, name fromStudent").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
多个属性查询,返回Student
- //可以使用hql返回Student对象
- //需要提供构造函数
- Liststudents = session.createQuery("select new Student(id, name) fromStudent").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getId()+ ", " + student.getName());
- }
使用别名
- //可以使用别名
- Liststudents = session.createQuery("select s.id, s.name from Students").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
- //可以采用as命名别名
- Liststudents = session.createQuery("select s.id, s.name from Student ass").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
2、实体对象查询【重要】
* N + 1问题,就是发出了N+1条sql语句
1:首先发出查询对象id列表的语句
N:根据id到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句
*list和iterate的区别?
list:默认情况下list每次都会发出sql语句,list会将数据放到缓存中,而不利用缓存
iterate:默认情况下iterate利用缓存,如果缓存中不存在会出现N+1问题
- //返回Student对象的集合
- //可以忽select关键字
- Liststudents = session.createQuery("from Student").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getName());
- }
- /**
- * 采用list查询实体对象会发出一条查询语句,取得实体对象数据
- *
- * Hibernate: select student0_.id as id0_,student0_.name as name0_,
- * student0_.createTime as createTime0_,student0_.classesid as classesid0_
- * from t_student student0_
- */
- Liststudents = session.createQuery("from Student").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getName());
- }
3、条件查询【重要】
*可以采用拼字符串的方式传递参数
- //可以拼串
- Liststudents = session.createQuery("select s.id, s.name from Student s wheres.name like '%0%'").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
* 可以采用?来传递参数(索引从0开始)
- //可以使用?方式传递参数
- //索引从0开始,不同于jdbc从1开始
- //值能使用单引号引起来
- //Liststudents = session.createQuery("select s.id, s.name from Student s wheres.name like ?").list();
- // Queryquery = session.createQuery("selects.id, s.name from Student s where s.name like ?");
- // query.setParameter(0,"%0%");
- // Liststudents = query.list();
- //方法链编程,建议采用此种方式
- Liststudents = session.createQuery("select s.id, s.name from Student s wheres.name like ?")
- .setParameter(0,"%0%")
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
* 可以采用:参数名 来传递参数
- //可以采用:参数名 的方式传递参数
- Liststudents = session.createQuery("select s.id, s.name from Student s wheres.name like :myname")
- .setParameter("myname", "%0%")
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
*如果传递多个参数,可以采用setParamterList方法
- //采用?方式,查询学号为1,2,3,4,5的学生
- Liststudents = session.createQuery("select s.id, s.name from Student s wheres.id in(?, ?, ?, ?, ?)")
- .setParameter(0,1)
- .setParameter(1,2)
- .setParameter(2,3)
- .setParameter(3,4)
- .setParameter(4,5)
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
*在hql中可以使用数据库的函数,如:date_format
- //采用:参数名 方式,查询学号为1,2,3,4,5的学生
- Liststudents = session.createQuery("select s.id, s.name from Student s wheres.id in(:ids)")
- .setParameterList("ids",new Object[]{1, 2, 3, 4, 5})
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
- //查询2009-08的学生,可以调用mysql的日期格式化函数
- Liststudents = session.createQuery("select s.id, s.name from Student s wheredate_format(s.createTime, '%Y-%m')=?")
- .setParameter(0, "2009-08")
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
- SimpleDateFormatsdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- //查询2009-08-01到2009-08-20的学生,可以调用mysql的日期格式化函数
- Liststudents = session.createQuery("select s.id, s.name from Student s wheres.createTime between ? and ?")
- .setParameter(0,sdf.parse("2009-08-01 00:00:00"))
- .setParameter(1,sdf.parse("2009-08-20 23:59:59"))
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
4、hibernate直接使用sql语句查询
- Liststudents = session.createSQLQuery("select * from t_student").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
5、外置命名查询
* 在映射文件中使用<query>标签来定义hql
* 在程序中使用session.getNamedQuery()方法得到hql查询串
- <querynamequeryname="queryStudent">
- <![CDATA[
- selects from Student s where s.id <?
- ]]>
- </query>
- List students =session.getNamedQuery("queryStudent")
- .setParameter(0,10)
- .list();
- for (Iteratoriter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getName());
- }
6、查询过滤器
*在映射文件中定义过滤器参数
*在类的映射中使用过滤器参数
*在程序中必须显示的启用过滤器,并且为过滤器参数赋值
- <filter-defnamefilter-defname="testFilter">
- <filter-paramtypefilter-paramtype="integer" name="myid"/>
- </filter-def>
- session.enableFilter("testFilter")
- .setParameter("myid",10);
- Liststudents = session.createQuery("from Student").list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getName());
- }
7、分页查询【重要】
*setFirstResult(),从0开始
*setMaxResults(),每页显示的记录数
- Liststudents = session.createQuery("from Student")
- .setFirstResult(1)
- .setMaxResults(2)
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getName());
- }
8、对象导航查询【重要】
- Liststudents = session.createQuery("from Student s where s.classes.name like'%2%'")
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Studentstudent = (Student)iter.next();
- System.out.println(student.getName());
- }
9、连接查询【重要】
* 内连接
* 外连接(左连接/右连接)left join right join
- Liststudents = session.createQuery("select c.name, s.name from Student s joins.classes c")
- .list();
- for(Iterator iter=students.iterator(); iter.hasNext();) {
- Object[]obj = (Object[])iter.next();
- System.out.println(obj[0]+ ", " + obj[1]);
- }
10、统计查询【重要】
- Longcount = (Long)session.createQuery("select count(*) fromStudent").uniqueResult();
11、DML风格的操作(尽量少用,因为和缓存不同步)
- @SuppressWarnings("unused")
- //Studentstudent = (Student)session.load(Student.class, 1);
- Studentstudent = (Student)session.get(Student.class, 1);
- //数据库表中更新了,但缓存不更新
- //所以一般不建议使用,除非有必须,如遇到性能问题
- session.createQuery("updateStudent s set s.name=? where s.id<?")
- .setParameter(0,"王五")
- .setParameter(1,5)
- .executeUpdate();
- //student= (Student)session.load(Student.class, 1);
- student= (Student)session.get(Student.class, 1);
- System.out.println("student.name="+ student.getName());