报表查询用于对数据分组和统计,完整的HQL语法:
[select...] from ... [where...] [group by... [having...]] [order by...]
4.1 投影查询
指查询结果仅包含部分实体或实体的部分属性。投影是通过select关键字实现。
from Customer c join c.orders o where o.orderNumber like 'T%'
//会检索出Customer 及关联的Order对象
select c from Customer c join c.orders o where o.orderNumber like 'T%'
//结果中只包含Customer对象
select c.id,c.name,o.orderNumber from Customer c join c.orders o where ...
//返回的list中,放的是Object[],对应选择的属性集合
4.1.1 动态实例化查询结果
select c.id,c.name,o.orderNumber from Customer c join c.orders o where ...
//返回的list中,放的是Object[],对应选择的属性集合
该查询结果返回的list集合存放的是关系数据,List中的每个元素代表查询结果的一条记录,可以定义一个CustomerRow类来包装这些记录,使程序代码能完全运用面向对象的语义来访问查询结果集。CustomerRow类采用JavaBean的形式,他的属性与Select语句中选择的实体的属性对应。
Iterator it = session.createQuery("select new mypack.CustomerRow(c.id,c.name,c.orderNumber) from Customer c join c.orders o where o.orderNumber like 'T%'").list().iterator();
while(it.hasNext()){
CustomerRow row = (CustomerRow)it.next();
}
4.1.2 过滤查询结果中的重复元素
select c.name from Customer c
返回的结果可能会有重复的名字,可以使用HashSet顾虑结果,或使用distinct关键字
select distinct c.name from Customer c
4.2 使用聚集函数
HQL查询语句中可调用的聚集函数:
a. count():统计记录条数
Integer count = (Integer)session.createQuery("select count(*) from Customer c").uniqueResult();
Integer count = (Integer)session.createQuery("select count(distinct c.name) from Customer c").uniqueResult();
b.min():求最小值
c.max():求最大值
Object [] os = (Object[])session.createQuery("select max(c.age),min(c.age) from Customer c").uniqueResult();
d.sum():求和
e.avg():求平均值
Float age = (Float)session.createQuery("select avg(c.age) from Customer c ").uniqueResult();
4.3 分组查询
(1)按照姓名分组,统计具有相同姓名的记录数目:
session.createQuery("select c.name,count(c) from Customer c group by c.name")
......
4.4 优化报表查询的性能
当select语句仅仅选择查询持久化类的部分属性时,Hibernate返回的查询结果为关系数据,而非持久化对象。eg:
from Customer c inner join c.orders o group by c.age
select c.Id,c.Name,c.age,o.ID,o.ORDER_NUMBER,o.CUSTOMER_ID from Customer c inner join c.orders c group by c.age
以上两条HQL能查询处相同的数据,不同的是,前者返回的是Customer和Order持久化对象,他们位于Session的缓存中;后者返回的是关系数据,不会占用session的缓存,可以被JVM回收。
报表查询一般会处理大量数据,如果使用第一种方式会导致大量Customer和Order持久化对象一直位于Session中,session还必须负责这些对象的同步,会降低性能!
5、高级查询技巧
5.1 动态查询
如果在程序运行前就明确了查询语句的内容(也称为静态查询),应该考虑HQL查询方式;但是如果只有在运行时才能明确查询语句的内容(也称为动态查询)QBC比HQL更加方便。
5.2 集合过滤
假设Customer的orders集合使用延迟策略,当调用customer.getOrders().iterator()方法,就会初始化orders集合,会加载所有与Customer关联的Order持久化对象。这种方式有两大不足:
a. 如果有1000个Order对象与Customer关联,就会加载1000个Order对象,而这些并不一定是都需要的,会因为加载多余的Order对象而影响性能。(如:访问价格大于100的Order对象)
b. 不能对orders集合排序
解决方法:
a. 通过HQL或QBC查询
session.createQuery("from Order o where o.customer= :customer and o.price>100 order by o.price");
b. 使用集合过滤
Query query = session.createFilter(customer.getOrders(),"where this.price>100 order by this.price")
session.createFilter()方法与你过来过虑集合,具有以下特点:
a. 返回Query类型的实例
b. 第一个参数指定一个持久化对象的集合,这个集合是否已经被初始化并没有关系,但是它所属的对象必须出于持久化状态
c. 第二个参数指定过滤条件,由HQL查询语句组成
d.不管持久化对象的集合是否已经被初始化,Query的list()方法都会进行查询,以上代码对应的SQL:
select ID,ORDER_NUMBER,PRICE from ORDERS where CUSTOMER_ID=1 and PRICE>100 order by PRICE;
e.如果Customer对象的orders集合已经被初始化,为了保证session的缓存中不会出现OID相同的Order对象,list()方法不会在创建Order对象,仅仅返回session中的引用。
f. 如果Customer对象的orders集合还没有被初始化,Query的list方法会创建相应的order对象,但是不会初始化Customer对象的orders集合。
集合过滤出了用于为集合排序或设置约束条件,还有其他用途:
(1)为Customer对象的orders集合分页
List result = session.createFilter(customer.getOrders(),"order by this.price asc")
.setFirstResult(10)
.setMaxResults(50)
.list();
(2)检索Customer对象的orders集合中Order对象的订单编号
List result = session.createFilter(customer.getOrders(),"selct this.orderNumber").list();
(3)检索数据库中与Customer对象的orders集合中Order对象的价格相同的所有Order对象:
List result = session.createFilter(customer.getOrders(),"select other from Order other where other.price = this.price").list();
(4)检索Order对象的lineItems集合中LineItem对象的Item
List result = session.createFilter(order.getLineItems(),"select this.item").list();
5.3 子查询
from Customer c where 1<(select count(o) from c.orders o )
子查询必须放在括号内。
(1)子查询可以分为相关子查询和无关子查询。相关子查询指子查询语句引用了外层查询语句定义的别名,而无关子查询指子查询语句与外层查询语句无关。
(2)HQL子查询依赖于底层数据库对子查询的支持能力。MySQL从4.1.x才开始支持子查询。如果希望应用程序能进行移植,应避免使用HQL的子查询功能。无关子查询语句可以改写为单独的查询语句,相关子查询语句可以改写为连接查询和分组查询。eg:
select c from Customer c join c.orders o group by c.id having count(o)>1
(3)如果子查询返回多条记录,可以用以下关键字来量化。
--all: 表示子查询返回的所有记录
--any: 表示子查询语句返回的任意一条记录
--some: 与'any'等价
--in: 与'any'等价
--exists: 表示子查询语句至少返回一条记录。
(4)如果子查询语句查询的是集合,HQL提供了缩写语法,eg:
Iterator it = session.createQuery("from Customer c where :order in elements(c.orders)")
.setEntity("order",order)
.list()
.iterator();
以上elements()函数等价于一个子查询语句:
from Customer c where :order in (from c.orders)
HQL提供了一组操作集合的函数或者属性
--size函数或size属性:获得集合中元素的数目
--minIndex函数或属性:对于建立了索引的集合,获得最小的索引
--maxIndex函数或属性:对于建立了索引的集合,获得最大的索引
--minElement函数或属性:对于包含基本类型元素的集合,获得集合中取值最小的元素
--maxElement函数或属性:对于包含基本类型元素的集合,获得集合中取值最大的元素
--elements函数:获得集合中所有的元素
from Customer c where c.orders.size>0 or
from Customer c where size(c.orders)>0
5.4 本地SQL查询
为了把SQL查询返回的关系数据映射为对象,需要在SQL查询语句中为字段指定别名。
String sql = "select cs.ID as {c.id},cs.NAME as {c.name},cs.AGE as {c.age} from CUSTOMERS cs where cs.ID=1";
Query query = session.createSQLQuery(sql,"c",Customer.class);
// session.createSQLQuery(sql,"类别名",Customer.class);类的别名必须位于大括号里。
String sql = "select {c.*},{o,*} from CUSTOMERS c inner join ORDERS o where c.ID=o.CUSTOMER_ID";
Query query = session.createSQLQuery(sql,new String[]{"c","o"},new Class[]{Customer.class,Order.class});
SQL放入映射文件中:
6.查询性能优化
Hibernate主要从以下几方面来优化查询性能
(1)降低访问数据库的频率,减少select语句的数目,实现手段包括:
a.使用迫切左外连接或迫切内连接检索策略
b.对延迟检索或立即检索策略设置批量检索数目
c.使用查询缓存
(2)避免多余加载程序不需要访问的数据,手段:
a.使用延迟检索策略
b.使用集合过滤
(3)避免报表查询数据占用缓存,实现手段为利用投影查询功能,查询出实体的部分属性。
(4)减少select语句中的字段,从而降低访问数据库的数据量,实现手段为利用Query的iterate()方法。
6.1 iterate()方法
Query接口的iterate()方法和list()方法都能执行SQL查询语句,但是前者在有些情况下能轻微提高查询性能。
iterate方法首先到数据库中检索ID字段,然后根据ID字段到Hibernate的第一级缓存以及第二季缓存中查找匹配的对象,如果存在就直接加入到查询结果集中,否则就执行额外的select语句,根据ID字段到数据库中查询该对象。而list方法会检索出所有的数据,只是如果ID对应的对象存在缓存中就不再进行封装了,直接返回。
6.2 查询缓存
对于查询语句,如果启用了查询缓存,当第一次执行查询语句时,hibernate会把查询结果存放在第二级缓存中,以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能!
注:如果查询结果中包含实体,第二级缓存只会存放实体的OID,而对于投影查询,第二级缓存会存放所有的数据值。
查询缓存适合于以下场合:
a.在应用程序运行时经常使用的查询语句
b.很少对查询语句关联的数据库数据进行插入、删除、更新操作
对查询语句启用查询缓存的步骤如下:
(1)配置第二级缓存
(2)在Hibernate的hibernate.properties配制文件中设置查询缓存属性:
hibernate.cache.use_query_catch=true;
(3)设置了第二步还不够,在执行时还不会启用查询缓存。对于希望启用查询缓存的查询语句,应该调用Query的setCatchable(true),启用对该查询语句的缓存。如果希望更加精力度的控制查询缓存,可以设置缓存区域:
query.setCatcheRegion("customerQueries");
Hibernate提供了三种和查询相关的缓存区域:
a.默认的查询缓存区域:cache.StandardQueryCache
b.用户自定义的查询缓存区域(也叫命名缓存)//query.setCatcheRegion("customerQueries");
c.时间戳缓存区域:cache.UpdateTimestampCache.
a、b都用于存放查询结果,而c存放了对与查询结果相关的表进行了插入、更新、删除操作的时间戳。Hibernate通过c类判断缓存的查询结果是否过期,运行过程如下:
(1)在T1时刻执行查询语句,把查询结果存放在QueryCache区域,该区域的时间戳为T1时刻
(2)在T2时刻对与查询结果相关的表进行插入、更新、删除操作,Hibernate把T2时刻存放在UpdateTimestampCache区域。
(3)在T3时刻执行查询语句前,先比较QueryCache区域的时间戳河UpdateTimestampCache区域的时间戳,如果T2>T1,那么就丢弃存放在QueryCache区域的查询结果,重新到数据库中查询数据,再把结果放到QueryCache区域,否则直接返回查询结果。
可见,当前应用程序会自动刷新缓存的结果,但是如果其他应用程序对数据库进行了修改,Hibernate无法检测这一变化,此时必须由应用程序负责监测这一变化,然后手工刷新查询结果。Query.setForceCacheRefresh(true)方法允许手工刷新结果。
7、小结