攻城狮在路上(壹) Hibernate(十四)--- Hibernate的检索方式(下)
本节介绍HQL和QBC的高级用法:各种连接查询、投影查询、报表查询、动态查询、集合过滤和子查询等。另外将归纳优化查询程序代码,从而提高查询性能的各种技巧。
一、连接查询:
HQL与QBC支持的各种连接类型:
在程序中指定的连接查询类型 | HQL语法 | QBC语法 | 适用范围 |
内连接 | inner join 或者join |
Criteria.createAlias() | 适用于有关联关系的持久化类,并且在映射文件中对这种关联关系做了映射 |
迫切内连接 | inner join fetch 或者join fetch |
不支持 | |
隐式内连接 | 支持 | 不支持 | |
左外连接 | left outer join 或者left join | 不支持 | |
迫切左外连接 | left outer join fetch 或者left join fetch | FetchMode.JOIN | |
右外连接 | right outer join 或者 right join | 不支持 | |
交叉连接 | Class A, Class B | 不支持 | 适用于不存在关联关系的持久化类 |
1、默认情况下关联级别的运行时检索策略:
可以在映射文件中设置关联级别的立即检索、延迟检索或迫切左外连接检索。注意Query API会忽略迫切左外连接的配置。
若程序代码中没有显式指定与Customer类关联的Order对象的检索策略,那么将采用Customer.hbm.xml映射文件对orders集合设置的检索策略。
有个例外:HQL会忽略映射文件设置的迫切左外连接检索策略,详见下表。
HQL和QBC运行时对Customer类的orders集合使用的检索策略:
Customer.hbm.xml文件对orders集合设置的检索策略 | HQL | QBC |
立即检索 | 立即检索 | 立即检索 |
延迟检索 | 延迟检索 | 延迟检索 |
迫切左外连接检索 | 立即检索 | 迫切左外连接检索 |
2、迫切左外连接:
若程序中显式指定了与Customer关联的Order对象的检索策略,则会覆盖Customer.hbm.xml中的设置。
实例代码:
//HQL检索方式 List result = session.createQuery("from Customer c left join fetch c.orders o where c.name like 'T%'").list(); //QBC检索方式: list result = session.createCriteria(Customer.class) .setFetchMode("orders", FetchMode.JOIN) .add(Restrictions.like("name", "T", MatchMode.START)).list(); for(Iterator it = result.iterator(); it.hasNest()) { Customer customer = (Customer)it.next(); }
HQL中,left join fetch关键字表示迫切左外连接检索策略。在QBC中,FetchMode.JOIN表示迫切左外连接检索策略。
使用Query或则Criteria的list()方法返回的集合中存放Customer对象的引用,每个Customer对象的orders集合都被初始化,存放所有管理的Order对象。
注意:当使用迫切左外连接检索策略时,查询结果中可能会包含重复元素,可以通过一个HashSet来过滤重复元素。
3、左外连接:
实例代码:
//HQL检索方式 //如果希望list()返回的集合中仅包含Customer对象,在HQL语句中使用select关键字即可。 List result = session.createQuery("from Customer c left join c.orders o where c.name like 'T%'").list(); for(Iterator paris = result.iteraor(); pairs.hasNext() { Object[] pair = (Object[])pairs.next(); Customer customer = (Customer)pair[0]; Order order = (Order)pair[1]; //如果orders集合使用延迟加载策略,以下代码会初始化Customer对象的orders集合 customer.getOrders().iterator(); }
4、内连接:
在HQL中,使用inner join关键字表示内连接。另外QBC也支持内连接(createAlias("orders", "o"))。
实例代码:
List result = session.createQuery("from Customer c inner join c.orders o where c.name like 'T%'").list(); for(Iterator paris = result.iteraor(); pairs.hasNext() { Object[] pair = (Object[])pairs.next(); Customer customer = (Customer)pair[0]; Order order = (Order)pair[1]; //如果orders集合使用延迟加载策略,以下代码会初始化Customer对象的orders集合 customer.getOrders().iterator(); }
QBC方式默认仅检索出Customer对象,若要希望QBC返回的集合也包含成对的Customer和Order对象,可以调用Critiria的setResultTransformer()方法。
5、迫切内连接:
通过inner join fetch关键字来指定,此种方式会覆盖映射文件中指定的检索策略。
QBC不支持迫切内连接。
6、隐式内连接:
HQL支持隐式内连接,QBC不支持。
7、右外连接:
在HQL中,使用right join关键字表示右外连接。
8、使用SQL风格的交叉连接盒隐式内连接:
此种情况多用于不存在关联关系的两个类:
from Customer, Student
9、关联级别运行时的检索策略:
该节时对关联级别运行时的检索策略的总结:
A、如何在HQL或QBC程序代码中没有显示的指定检索策略,将使用映射文件配置的检索策略,例外情况是:HQL总是忽略配置文件中设置的迫切左外连接检索策略。
B、如果在HQL或QBC程序代码中显示指定了检索策略,就会覆盖映射文件配置的检索策略。
C、HQL支持各种连接查询:left join fetch\left join\inner join fetch\inner join\right join
HQL在各种连接方式下的运行时行为:
连接方式 | 对应的SQL查询语句 | orders集合的检索策略 | 查询结果集中的内容 |
无连接 | 查询单个CUSTOMERS表 | 延迟检索策略 | 集合中包含Customer类型的元素; 集合中无重复元素; Customer对象的orders集合没有被初始化。 |
迫切左外连接 | 左外连接查询CUSTOMERS和ORDERS表 | 迫切左外连接检索策略 | 集合中包含Customer类型的元素; 集合中可能有重复元素; Customer对象的orders集合被初始化。 |
左外连接 | 左外连接查询CUSTOMERS和ORDERS表 | 延迟检索策略 | 集合中包含对象数组类型的元素,每个对象数组包含一对Customer和Order对象,不同的对象数组可能重复引用同一个Customer对象; Customer对象的orders集合没有被初始化。 |
内连接 | 内连接查询CUSTOMERS和ORDERS表 | 延迟检索策略 | 同上 |
迫切内连接 | 内连接查询CUSTOMERS和ORDERS表 | 迫切内连接检索策略 | 集合中包含Customer类型的元素; 集合中可能有重复元素; Customer对象的orders集合被初始化。 |
右外连接 | 右外连接查询CUSTOMERS和ORDERS表 | 延迟检索策略 | 同左外连接 |
二、投影查询:
投影查询是指查询结果中仅包含部分实体或实体的部分属性。投影式通过select关键字实现的。
实例代码:
//查询结果仅包含Customer对象 select c from Customer c join c.orders o where o.orderNumber like 'T%' //仅选择对象的部分属性 select c.id,c.name,c.orderNumber from Customer c;
1、动态实例化查询结果:
上面的示例中list()返回的集合存放的是关系数据,而不是对象,可以定义一个类来包装这些属性:
selct new xxx.CustomerBean(c.id, c,name, o.orderNumber) from Customer c join c.orders o where o.orderNumber like 'T%'
2、过滤查询结果中的重复元素:
前面讲过使用Set来过滤重复元素的方式,这里提供例外一种方式:
select distint c.name from Customer c
三、报表查询:
报表查询用于对数据分组和统计,用select关键字选择需要查询的数据,使用group by关键字对数据分组,使用having关键字对分组数据设定约束条件。
HQL语法格式:只有from关键字是必需的。
[select] from ... [where] [group by ... [having...]] [order by...]
1、使用聚集函数:count()\min()\max()\sum()\avg()\
2、分组查询:类似于SQL语句。
3、优化报表查询的性能:主要是考虑查询的报表数据没有必要存放于Session的缓存中。实现方式通过上面获取关系数据的方式即可。
四、高级查询技巧:
概述:
动态查询:在程序运行时动态的决定查询语句的内容。主要通过QBC或QBE实现。
集合过滤:对集合进行过滤,实现对集合的排序、设置约束条件和分页查询等。
子查询:在HQL查询语句中嵌入子查询语句。
本地SQL语句:用本地数据库的SQL方言来查询数据。
查询结果转换器:对查询结果进行过滤或重组,指定查询结果的内容。
1、动态查询:
如果在程序运行前就明确查询语句的内容,优先使用HQL查询方式;若在程序运行时才能明确查询语句的内容时,QBC更加方便。
主要方式是在程序内通过条件判断来决定添加何种查询条件。
2、集合过滤:
通过session.createFilter(customer.getOrders(),"where ...");
第一个参数指定一个持久化对象的集合
第二个参数指定过滤条件,它由合法的HQL查询语句组成。
3、子查询:
HQL支持在where子句中嵌入子查询语句。要求底层数据库支持子查询,例如MySQL从4.1.x才开始支持子查询。
可以使用all\any\some\in\exists关键字。
另外HQL提供了一组操纵集合的函数或者属性:
size()函数或者size属性:获得集合中元素的数目。
minIndex()函数或者minIndex属性:对于建立了索引的集合,获取最小的索引。
maxIndex()函数或者maxIndex属性:对于建立了索引的集合,获取最大的索引。
minElement():对包含基本类型元素的集合,获得集合中取值最小的元素。
maxElement():对包含基本类型元素的集合,获得集合中取值最大的元素。
elements():获得集合中的所有元素。
4、本地SQL查询:
session.createSQLQuery("select * from CUSTOMERS");
若要明确指定每一列的Java数据类型:
addScalar("ID", Hibernate.LONG);
addEntity(Cusmomer.class);把结果集中的关系数据映射为对象。
addJoin():用于连接查询。
上述连接查询方式中,如果在程序代码中直接写SQL语句,会增加维护代码的难度,不建议此种硬编码的方式,替代方案是使用下面的方式:
<sql-query name="findOrderByName"> <return alias="o" class="mypack.Order"/> <return-join alias="c" property="o.customer"/> select {o.*},{c.*} from ORDERS o join CUSTOMERS c on o.CUSTOMER_ID=c.ID where c.NAME=:name </sql-query> Query query = session.getNamedQuery("findOrderByName").setParameter("name","TOM"); List list = query.list();
5、查询结果转换器:
在Criteria中,通过Criteria.setResultTransformer()来指定常量:
Criteria.ROOT_ENTITY:默认值。查询结果中仅包含根节点的实例,可能有重复值。
Criteria.DISTINCT_ROOT_ENTITY:查询结果中仅包含根节点的实例,没有重复值。
Criteria.ALIAS_TO_ENTITY_MAP:查询结果中包含根节点的实例及被关联的实例,存放在Map中。
在HQL检索方式和本地SQL检索方式中,通过setResultTransformer(Transformes.aliasToBean(CustomerDTO.class));
五、查询性能优化:
1、Hibernate优化查询的手段:
A、减低访问数据库的频率,减少select语句的数量。实现手段包括:
1、使用迫切左外连接或者迫切内连接检索策略。
2、对延迟加载或立即检索策略设置批量检索数目。
3、使用查询缓存。
B、避免多余加载程序中不需要访问的数据:实现手段包括:
1、使用延迟加载策略。
2、使用集合过滤。
C、避免报表查询数据占用缓存。实现手段为利用投影查询功能,查询出实体的部分属性。
D、减少select语句中字段的数量,从而减低访问数据库的数据量。
2、iterate()方法:
在启用二级缓存的情况下,可以轻微的提高查询性能。
3、查询缓存:
如果查询结果中包含实体,查询缓存只会存放实体的OID;对于投影查询,查询缓存会存放所有的数据值。
1、适用场合:
A、在应用程序运行时经常使用的查询语句。
B、很少对查询语句检索到的数据进行插入、删除或更新操作。
2、启用查询缓存的步骤:
A、配置第二级缓存。(详见后续的说明)
B、配置查询缓存属性:hibernate.cache.user_query_chche=true
C、对于希望启用查询缓存的查询语句,调用Query接口的setCacheable(true);
D、若希望更加精粒度的控制查询缓存,可以设置缓存区域。详见后续笔记说明。
六、总结:
比较HQL与QBC的优缺点:
比较方面 | HQL检索方式 | QBC检索方式 |
可读性 | 优点:和SQL语言比较接近,比较容易读懂 | 缺点:QBC把查询语句肢解为一组Criteria实例,可读性差 |
功能 | 优点:功能最强大,支持各种连接查询 | 缺点:没有HQL的强大,例如不支持报表查询和子查询,而且对连接查询也做了很多限制 |
查询语句形式 | 缺点:应用程序必须提供基于字符串形式的HQL查询语句 | 优点:QBC检索方式封装了基于字符串形式的查询语句,提供了更加面向对象的接口。 |
何时被解析 | 缺点:HQL查询语句只有在运行时才会被解析 | 优点:QBC在编译时就能被解析,因此更加容易排错。 |
可扩展性 | 缺点:不具有可扩展性 | 优点:允许用户扩展Criteria接口 |
对动态查询语句的支持 | 缺点:尽管支持生成动态查询语句,但是编程很麻烦 | 优点:适合于生成动态查询语句 |