Hibernate的查询语言之HQL(二)——Hibernate查询的from字句

时间:2021-08-18 18:04:06

  from 是最简单的HQL语句,也是最基本的HQL语句。from 关键字后紧跟持久化类的类名。例如:

 from Person

  表明从Person持久化类中取出全部的实例。

  大部分时候,推荐位该Person的每个实例取一个别名。例如:

 from Person as p

  上面的as是可选的,但为了增加可读性,建议保留。

  from 后面还可以同时出现多个持久化类,此时将产生一个笛卡尔积或跨表连接,但实际上这种用法很少使用,因为通常我们可能需要使用跨表连接时,可以考虑使用隐士连接或者显示连接,而不是直接在from后紧跟多个表名。

  

  关联和连接

  当程序需要从多个数据表中取得数据时,SQL语句将会考虑使用多表连接查询。Hibernate使用关联映射来处理底层数据表之间的连接,一旦我们提供正确的映射后,当程序通过Hibernate进行持久化访问时,将可利用Hibernate的关联进行连接。

  HQL支持两种关联连接(join)形式: 隐式(implicit)与显式(explicit)。

  隐式连接形式不适用 join 关键字,使用英文点号(.)来隐式连接关联实体,而Hibernate底层将自动进行关联查询。例如如下HQL语句

 //查询Person持久化实体
from Person as p
where p.myEvents.titile > :title

  上面的p.myEvents属性的实质是一个持久化实体,因此Hibernate底层隐式自动进行连接查询。

  

  显式连接则需要使用 xxx join 关键字。例如下面的语句:

 from Person p
inner join p.myEvents event
where event.happenDate < :endDate

  使用显式连接 时可以为相关联的实体,甚至是关联集合中的全部元素指定一个别名。

  Hibernate支持的HQL连接直接借鉴了 SQL99 多表查询的关键字,可使用如下几种连接方法。

  》inner join (内连接),可简写成 join。

  》left outer join(左外连接),可简写成 left join。

  》right outer join(右外连接),可简写成right join。

  》full join(全连接),并不常用。

  使用显式连接时,还可通过HQL的with关键字来提供额外的连接条件,例如如下HQL语句:

 from Person p
inner join p.myEvents event
with p.id > event.id
where event.happenDate < :endDate

  Hibernate会将这种显式连接转换成SQL99多表连接的语法,所以HQL语句中with关键字的作用等同于SQL99中 on 关键字的作用;都是用于指定连接条件。通过在HQL语句中使用with关键字,可以让HQL语句执行非等值连接查询。

  

  不难发现 要想完全掌握HQL,不懂SQL是完全不行的。

  还有一点必须指出:由于此处的inner join, left join, right join, full join的实质依然是基于底层的SQL的内,左,外连接的,所以如果底层SQL不支持这些外连接,那么执行相应的HQL时就会相应的引发异常。

  对于隐式连接和显式连接还有如下两点区别:

  》隐式连接底层将转换为SQL99 的交叉连接,显式连接底层将转换成SQL99 的 inner join, left join, right join等连接。

  对于from Person p where p.myEvent.title > :title这条隐式连接的HQL语句,执行HQL查询后将看到产生如下所示的SQL语句。

 select
person0_.person_id as person1_0_,
person0_.name as name_0,
person0_.age as age0_,
person0_.event_id as event4_0_
from
person person0_ cross
join
event myevent1_
where
person0_.event_id = myevent1_.event_id
and myevent1_.title > ?

  而对于from Person p left join p.myEvents event where event.happenDate < :endDate 这条显式连接的HQL语句,执行HQL查询后将看到产生如下所示的SQL语句:

 select
person0_.person_id as person1_0_,
person0_.name as name0_,
person0_.age as age0_,
person0_.event_id as event4_0_
from person person0_
left outer join
event myevent1_
on person0_.event_id = myevent1_.event_id
where
myevent1_.happenDate < ?

  对比这两条SQL语句,不难发现第一条SQL语句是SQL99的交叉连接,而第二条SQL语句则是SQL99的左外连接语法——具体到底使用哪种连接方法,取决于HQL语句的显示连接使用了哪种连接方式。

  》隐式连接和显示连接查询后返回的结果不同。

  当HQL语句中省略select关键字时,使用隐式连接查询返回的结果是多个被查询实体组成的集合。如上面第一条SQL语句所示,它只选择person表中的数据列,所以查询得到的结果是Person对象组成的集合。

  当使用显式连接查询的HQL语句中省略select关键字时,返回的结果也是集合,但集合元素是被查询持久化对象,所有被关联的持久化对象所组成的数组。如上面的第二条SQL语句所示,他同时选择了person,event表中的所有数据列,查询得到的结果集的每条记录即包含了Person实体的全部属性,也包含了MyEvent实体的全部属性。Hibernate会把每条记录封装成一个集合元素,用属于Person的属性创建Person对象,属于MyEvent的属性创建MyEvent对象.....多个持久化实体最后封装成一个数组来作为集合元素。

  如:

         Session session = HibernateSessionFactory.getSession();
Transaction tx = session.beginTransaction();
String hql = "from Person p join p.myEvents event";
List<Object[]> datas = session.createQuery(hql).list();
for(Object[] data:datas){
System.out.println(data[0]);//输出Person持久化实体对象
System.out.println(data[1]);//输出MyEvent持久化实体对象
}
tx.commit();
session.close();

  关于隐式连接和显示连接还有非常主要的一点需要指出,这是由Hibernate版本升级所引发的问题。在Hibernate3.2.2之前的版本, Hibernate会对所有关联实体自动使用隐式连接。对于如下HQL语句:

 

 from Person p
where p.myEvents.title = :eventTitle

  无论如何, Hibernate将对上面的p.myEvents.title自动使用隐式连接,因此上面的HQL语句总是有效的。  

  从Hibernate3.2.3以后, Hibernate改变了这种隐式连接的策略,还是对于这条同样的HQL语句,则可能出现以后几种情况:

  》如果myEvents 是普通组件属性,或单个的关联实体,则Hibernate会自动生成隐式内连接,上面的HQL语句依然有效。

  》如果myEvents是一个集合(包括1-N,N-N关联),那么系统将出现:QueryException异常,异常提示信息为:

 org.hibernate.QueryException: illegal attempt to dereference collection

  根据Hibernate的官方说法:这样可以使的隐式连接更具确定性(原文:This makes implicit joins more deterministic)

  为此, Hibernate推荐上面的HQL语句写成:

 from Person p
inner join p.myEvents e
where e.title = :eventTitle

  这条HQL语句将会返回一个集合,集合元素是Person实体和MyEvent实体组成的数组。

  如果只想获取Person组成的集合,则需要改写成:

 select p
from Person p
join p.myEvents e
where e.title = :eventTitle

  但上面的语句可能返回多个完全相同的Person对象,想一想SQL多表连接查询的结果就可知道原因了。

  如果想得到由Person实体组成的集合,且元素不重复,应该改为如下SQL语句:

 select distinct p
from Person p
inner join p.myEvents e
where e.title = :eventTitle

  也就是说,对于Hibernate 3.2.3以后的版本,如果关联实体是单个实例或单个的组件属性,HQL依然可以可以使用英文句点(.)来隐式连接关联实体或组件;但如果关联实体是集合(包括1-N关联,N-N关联和集合元素是组件等),则必须使用 xxx join 来显式连接关联实体或组件。

  对于有集合属性的,Hibernate默认采用延迟加载策略。如果Session被关闭,Person实例将无法访问关联的scores属性。

  为了解决该问题,可以在Hibernate映射文件中指定 lazy = "false" 来关闭延迟加载。

  还有一种方法,使用join fetch,例如:

 from Person p
join fetch p.scores

  上面的关键字将导致Hibernate在初始化Person对象时,同时抓取该Person关联的scores集合属性。

  使用join fetch时通常无需指定别名,因为相关联的的对象不应在where字句(或其他任何字句)中使用。而且被关联的对象也不会再被查询的结果中直接返回,而是应该通过其父对象来访问。

  使用fetch关键字时有如下几个注意点

  》fetch不应该与setMaxResult()或setFirstResult()公用。因为这些操作都是基于结果集的,而在预先抓取集合类时可能包含重复的数据,即无法预先知道精确的行数。

  》fetch不能独立与with条件一起使用。

  》如果在一次查询中fetch多个集合,可以查询返回笛卡尔积,因此请多加注意。

  》对bag映射而言,同时join fetch多个集合可能出现非预期结果,因此需要谨慎使用。

  》full join fetch 与 right join fetch是没有任何意义的。

  如果在映射文件映射属性时同时指定 lazy =  "true" 启动了延迟加载(这种延迟加载是通过字节码增强来实现的),然后程序里有希望预加载那些原本应延迟加载的属性,则可以通过 fetch all properties 来强制Hibernate立即抓取这些属性。例如:

  

 from Document fetch all properties order by name
from Document doc fetch all properties where lower(doc.name) like '%cats%'