E N D
第14章 用Hibernate检索数据 • 如果不知道所要寻找的对象的持久化标识,那么就无法通过load()或get()函数来获取持久化对象,这里就需要使用查询。Hibernate支持强大且易于使用的面向对象查询语言(HQL)。如果希望通过编程的方式创建查询,Hibernate提供了完善的按条件(Query By Criteria, QBC)以及按样例(Query By Example, QBE)进行查询的功能。也可以用本地SQL(native SQL)描述查询,Hibernate额外提供了将结果集(result set)转化为对象的支持。本章将详细介绍在Hibernate中如何查询数据
14.1 查询基础 • 序言中提到的几种查询方式虽然方法各有不同,但基本的操作顺序都是一样的,都需要准备查询条件、执行查询操作、处理查询结果。不同的方法需要选择不同的工具类,在个别情况下也可以混用。
14.1.1 执行查询 • HQL和本地SQL(native SQL)查询要通过为org.hibernate.Query的实例来表达。这个接口提供了参数绑定、结果集处理以及运行实际查询的方法。总是可以通过当前Session获取一个Query对象,如代码14-1所示,列出了几种使用Quey来执行查询的例子: • 这里提前介绍一下什么是HQL?HQL是HibernateQuery Language的缩写,HQL的语法很像SQL的语法,但HQL是一种面向对象的查询语言。因此,SQL的操作对象是数据表和列等数据对象,而HQL的操作对象是类、实例、属性等。下一节将详细介绍它。
14.1.2 用iterate()方法迭代查询结果 • 在某些情况下,可以使用iterate()方法得到更好的性能。这通常是预期返回的结果在session,或二级缓存(second-level cache)中已经存在时的情况。如若不然,iterate()会比list()慢,而且可能简单查询也需要进行多次数据库访问:iterate()会首先使用1条语句得到所有对象的持久化标识(identifiers),再根据持久化标识执行多条附加的select语句实例化实际的对象。
14.1.3 指定绑定参数 • 除了JDBC风格的问号(?)接收参数外,接口Query还提供了一种对命名参数(named parameters)参数进行绑定的方法。命名参数(named parameters)在查询字符串中是形如:name的标识符。如果按JDBC绑定参数,Hibernate对参数从0开始计数。 • 如代码14-3所示,给出了按顺序绑定参数和按名称绑定参数的例子。两者方法相同都是setString()或setInterger(),但方法的第一个参数不同,一个是整数表示顺序,另一个是String类型表示名称。
14.1.4 用Hibernate简单分页 • 如果需要指定结果集的范围(希望返回的最大行数/或开始的行数),应该使用Query接口提供的方法: • Query q = sess.createQuery("from DomesticPerson person"); • q.setFirstResult(20); • q.setMaxResults(10); • List persons = q.list();
14.1.5 可滚动遍历(Scrollable iteration) • 如果当前使用JDBC驱动支持可滚动的结果集(JDBC2.0后一般的主流数据库驱动都支持可滚动结果集),Query接口还可以使用ScrollableResults,允许用户在查询结果中上下移动游标, • 注意:使用此功能需要保持数据库连接(以及游标(cursor))处于一直打开状态。如果需要断开连接使用分页功能,请使用setMaxResult()/setFirstResult()。
14.2 HQL语句详解 • HQL的概念在本章的一开始就做过介绍,它是Hibernate专用的查询语言,语法与SQL类似,但操作的目标是对象。HQL 是完全面向对象的查询语言,因此可以支持继承和多态等特征。
14.2.1 HQL基础 • HQL 查询依赖于Query类,每个Query 实例对应一个查询对象。使用HQL 查询可按如下步骤进行: • (1)获取HibernateSession对象; • (2)编写HQL语句; • (3)以HQL语句作为参数,调用Session的createQuery方法创建查询对象; • (4)如果HQL语句包含参数,调用Query 的setXxx方法为参数赋值; • (5)调用Query对象的list等方法遍历查询结果。
14.2.2 用from子句指定数据表 • from子句是最简单的HQL语句,也是最基本的HQL语句。from关键字后紧跟持久化类的类名。例如: • from Person • 表明从Person 持久化类中选出全部的实例。大部分时候,推荐为该Person的每个实例起别名。例如: • from Person as p
14.2.3 select子句查询数据 • select子句虽然不是必须的(在SQL中select是必须的),但其作用非常重要,主要有以下几种用法: • (1)查询单个属性。select子句用于确定选择出的属性,当然select选择的属性必须是from 后持久化类包含的属性。例如:select p.name from Person as p • (2)查询组件中的属性。select可以选择任意属性,不仅可以选择持久化类的直接属性,还可以选择组件属性包含的属性,例如:select p.name.firstName from Person as p • (3)查询多个属性。查询语句可以返回多个对象和(或)属性,存放在 Object[]队列中,例如:select p.name , p.address from Person as p
14.2.3 select子句查询数据 • (4)把多个属性封装成一个list。select也支持将选择出的属性存入一个List对象中,例如:select new list(p.name ,p.address) from Person as p • (5)封装成对象。甚至可以将选择出的属性直接封装成对象,例如:select new ClassTest(p.name , p.address) from Person as p • 前提是ClassTest 支持p.name 和p.address 的构造器,假如p.name 的数据类型是String,p.address 的数据类型是String,则ClassTest 必须有如下的构造器:ClassTest(String s1, String s2) • (6)封装为map。select 还支持给选中的表达式命名别名,例如:select p.name as personName from Person as p
14.2.4 HQL中的聚集函数 • HQL也支持在选出的属性上,使用聚集函数。HQL支持的聚集函数与SQL完全相同,有如下5 个: • avg,计算属性平均值。 • count,统计选择对象的数量。 • max,统计属性值的最大值 • min,统计属性值的最小值。 • sum,计算属性值的总和。
14.2.5 多态查询 • 关于多态已经在“复杂映射关系”一章中“继承关系映射”有过详细介绍。HQL 查询语句被设计成能理解多态查询,from 后跟的持久化类名,不仅会查询出该持久化类的全部实例,还会查询出该类的子类的全部实例。如下面的查询语句: • from Person as p • 该查询语句不仅会查询出Person 的全部实例,还会查询出Person 的子类,如Manger、Employee、Customer的全部实例,前提是Person和Manger、Employee、Customer这些子类完成了正确的继承映射。
14.2.6 用where子句添加查询条件 • where子句用于筛选选中的结果,缩小选择的范围。如果没有为持久化实例命名别名,可以直接使用属性名引用属性。如下面的HQL查询语句: • from Person where name like 'tom%' • 如果为持久化实例命名了别名,则应该使用完整的属性名。 • from Person as p where p.name like "tom%" • 复合属性表达式加强了where子句的功能,例如如下HQL查询语句: • from Person p where p.address.country like "us%"
14.2.7 查询条件中的表达式 • HQL的功能非常丰富,where子句后支持的运算符异常丰富,不仅包括SQL的运算符,还包括EJB-QL 的运算符等。where 子句中允许使用大部分SQL支持的表达式: • 数学运算符+、–、*、/ 等。 • 二进制比较运算符=、>=、<=、<>、!=、like 等。 • 逻辑运算符and、or、not 等。 • in、not in、between、is null、is not null、is empty、is not empty、member of 和not member of 等。
14.2.8 用order by子句排序 • 查询返回的列表(list)可以根据类或组件属性的任何属性进行排序,例如: • from Person as p • order by p.name, p.age • 还可使用asc 或desc 关键字指定升序或降序的排序规则,例如: • from Person as p • order by p.name asc , p.age desc • 如果没有指定排序规则,默认采用升序规则。即是否使用asc关键字是没有区别的,加asc是升序排序,不加asc 也是升序排序。
14.2.9 用group by 子句分组 • 返回聚集值的查询可以对持久化类或组件属性的属性进行分组,分组所使用的group by 子句。看下面的HQL 查询语句: • select person.age,sum(person.weight), count(person) • from Person person • group by person. age
14.2.10 HQL的子查询 • 如果底层数据库支持子查询,则可以在HQL语句中使用子查询。与SQL中子查询相似的是,HQL 中的子查询也需要使用()括起来。如: • from Person as fatPerson • where fatPerson.weight >( select avg(person.weight) from Manager person ) • 如果select 中包含多个属性,则应该使用元组构造符: • from Person as p • where not ( p.name, p.age ) in ( • select m.name, m.age from Manger m • )
14.2.11 用fetch关键字来延时加载集合 • 对于集合属性,Hibernate默认采用延迟加载策略。例如,对于持久化类Person,有集合属性scores。加载Person 实例时,默认不加载scores 属性。如果Session 被关闭,Person实例将无法访问关联的scores 属性。 • 为了解决该问题,可以在Hibernate映射文件中取消延迟加载或使用fetch join,例如: • from Person as p join p.scores
14.2.12 HQL语句放入配置文件中 • HQL 查询还支持将查询所用的HQL语句放入配置文件中,而不是代码中。通过这种方式,可以大大提供程序的解耦。使用query元素定义命名查询,在mapping文件中与class元素是并列关系。
14.3 条件查询 • HQL极为强大,但是有些人希望能够动态的使用一种面向对象API创建查询,而非在他们的Java代码中嵌入字符串。对于那部分人来说,Hibernate提供了直观的Criteria查询API,Query By Criteria简称QBC。
14.3.1 添加查询条件 • 一个单独的查询条件是org.hibernate.criterion.Criterion接口的一个实例。Hibernate提供了一个工具类--org.hibernate.criterion.Restrictions,它定义了获得某些内置Criterion类型的工厂方法,来构造Restrictions对象,如: • //添加查询条件 • List persons = sess.createCriteria(Person.class) • .add( Restrictions.like("name", "Fritz%") ) • .add( Restrictions.between("weight", minWeight, maxWeight) ) • .list();
14.3.2 结果集排序 • 可以使用org.hibernate.criterion.Order来为查询结果排序,而不是去拼凑order by字符串。Order类同样提供了工厂方法来构建,另一种方式是通过Property类构建,如下代码两种方式都提供了: • //工厂方法来构建Order类 • List persons = sess.createCriteria(Person.class) • .add( Restrictions.like("name", "F%") • .addOrder( Order.asc("name") ) • .addOrder( Order.desc("age") ) • .setMaxResults(50) • .list(); • //Property类构建Order类 • List persons = sess.createCriteria(Person.class) • .add( Property.forName("name").like("F%") ) • .addOrder( Property.forName("name").asc() ) • .addOrder( Property.forName("age").desc() ) • .setMaxResults(50) • .list();
14.3.3 条件查询中的关联关系 • 可以使用createCriteria()非常容易的在互相关联的实体间建立约束。 • //使用createCriteria建立实体间的约束 • List persons = sess.createCriteria(Person.class) • .add( Restrictions.like("name", "F%") ) • .createCriteria("parts") • .add( Restrictions.like("name", "F%") ) • .list();
14.3.4 使用样例查询 • 样例查询顾名思义就是一个一个样例作为条件,把与这个样例有相同属性的实例查询出来。Hibernate提供了org.hibernate.criterion.Example类,允许用户通过一个给定实例来构建一个条件查询。如下代码,构造一个person实例作为查询条件,此时版本属性、标识符和关联被忽略。默认情况下值为null的属性将被排除。
14.3.5 离线(detached)查询和子查询 • Hibernate3提供了一个新的Criteria:DetachdCriteria。此类可脱离Session实例而存在。这样就可以将某些通用的Criteria查询条件进行抽离,每次使用前再与当前的Session绑定以获得更好的代码重用效果。 • 典型应用如下:DetachedCriteria类使在一个session范围之外创建一个查询,并且可以使用任意的Session来执行它。
14.4 直接使用SQL • Hibernate还支持使用SQL查询,使用SQL查询可以利用某些数据库的特性,或者用于将原有的JDBC 应用迁移到Hibernate应用上。使用命名的SQL查询还可以将SQL语句放在配置文件中配置,从而提高程序的解耦,命名SQL查询还可以用于调用存储过程。如果是一个新的应用,通常不要使用SQL查询。
14.4.1 使用SQL查询 • SQL查询是通过SQLQuery接口来表示的,SQLQuery 接口是Query 接口的子接口,因此完全可以调用Query 接口的方法: • setFirstResult(),设置返回结果集的起始点。 • setMaxResults(),设置查询获取的最大记录数。 • list(),返回查询到的结果集。 • 但SQLQuery 比Query 多了两个重载的方法: • addEntity,将查询到的记录与特定的实体关联。 • addScalar,将查询的记录关联成标量值。
14.4.2 SQL语句放入配置文件中 • 可以在映射文档中定义查询的名字,然后就可以象调用一个命名的HQL查询一样直接调用命名SQL查询。在文件中的SQL语句,使用<sql-query>标签,同样与映射文件中的class同一个级别,还有两个元素<return-join>和 <load-collection>。它们是用来连接关联以及将查询定义为预先初始化各个集合的
14.4.3 调用存储过程 • Hibernate3引入了对存储过程查询(stored procedure)和函数(function)的支持。要求这个存储过程或者函数必须返回一个结果集,作为Hibernate能够使用的第一个外部参数。因为存储过程本身完成了查询的全部操作,所以调用存储过程进行的查询无法使用setFirstResult()/setMaxResults()进行分页。 • 存储过程只能返回一个结果集,如果存储过程返回多个结果集,Hibernate将仅处理第一个结果集,其他将被丢弃。 • 如果在存储过程里设定SET NOCOUNT ON,将有更好的性能表现。当然也可以没有该设定。
14.5 数据过滤 • 数据过滤不是一种常规的数据查询方法,而是一种整体的筛选方法。数据过滤也可对数据进行筛选,因此,将其放在Hibernate的数据查询框架中介绍。如果一旦启用了数据过滤器,则不管数据查询,还是数据加载,该过滤器将自动作用于所有数据,只有满足过滤条件的记录才会被选出来。 • 过滤器与定义在类和集合映射文件上的“where”属性非常相似。它们的区别是过滤器可以带参数,应用程序可以在运行时决定是否启用指定的过滤器,以及使用什么样的参数值。而映射文件上的“where”属性将一直生效,且无法动态传入参数。过滤器的用法很像数据库视图,区别是视图在数据库中已经定义完成,而过滤器则还需在应用程序中确定参数值。
14.6 小结 • 数据的检索是数据库最重要的功能之一。在本章中介绍了几种Hibernate检索数据的方法。在实际应用中可根据需求灵活应用,除非必须应该尽量避免直接使用SQL语句,这将违背Hibernate的设计初衷,为将来数据库移植也带来隐患。