Hibernate查询语言
Hibernate配备了一种非常强大的查询语言,这种语言看上去很像SQL。但是不要被语法结构上的相似所迷惑,HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承、多态和关联之类的概念。
第15章HQL:Hibernate查询语言
Hibernate配备了一种非常强大的查询语言,这种语言看上去很像SQL。但是不要被语法结构上的相似所迷惑,HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承、多态和关联之类的概念。
15.1.大小写敏感性问题
除了Java类与属性的名称外,查询语句对大小写并不敏感。所以SeLeCT与sELEct以及SELECT是相同的,但是org.hibernate.eg.FOO并不等价于org.hibernate.eg.Foo并且foo.barSet也不等价于foo.BARSET。
本手册中的HQL关键字将使用小写字母.很多用户发现使用完全大写的关键字会使查询语句的可读性更强,但我们发现,当把查询语句嵌入到Java语句中的时候使用大写关键字比较难看。
15.2.from子句
Hibernate中最简单的查询语句的形式如下:
fromeg.Cat
该子句简单的返回eg.Cat类的所有实例。通常我们不需要使用类的全限定名,因为auto-import(自动引入)是缺省的情况。所以我们几乎只使用如下的简单写法:
fromCat
大多数情况下,你需要指定一个别名,原因是你可能需要在查询语句的其它部分引用到Cat
fromCatascat
这个语句把别名cat指定给类Cat的实例,这样我们就可以在随后的查询中使用此别名了。关键字as是可选的,我们也可以这样写:
fromCatcat
子句中可以同时出现多个类,其查询结果是产生一个笛卡儿积或产生跨表的连接。
fromFormula,Parameter
fromFormulaasform,Parameterasparam
查询语句中别名的开头部分小写被认为是实践中的好习惯,这样做与Java变量的命名标准保持了一致(比如,domesticCat)。
15.3.关联(Association)与连接(Join)
我们也可以为相关联的实体甚至是对一个集合中的全部元素指定一个别名,这时要使用关键字join。
fromCatascat
innerjoincat.mateasmate
leftouterjoincat.kittensaskitten
fromCatascatleftjoincat.mate.kittensaskittens
fromFormulaformfulljoinform.parameterparam
受支持的连接类型是从ANSISQL中借鉴来的。
innerjoin(内连接)
leftouterjoin(左外连接)
rightouterjoin(右外连接)
fulljoin(全连接,并不常用)
语句innerjoin,leftouterjoin以及rightouterjoin可以简写。
fromCatascat
joincat.mateasmate
leftjoincat.kittensaskitten
还有,一个"fetch"连接允许仅仅使用一个选择语句就将相关联的对象或一组值的集合随着他们的父对象的初始化而被初始化,这种方法在使用到集合的情况下尤其有用,对于关联和集合来说,它有效的代替了映射文件中的外联接与延迟声明(lazydeclarations).查看第20.1节“抓取策略(Fetchingstrategies)”以获得等多的信息。
fromCatascat
innerjoinfetchcat.mate
leftjoinfetchcat.kittens
一个fetch连接通常不需要被指定别名,因为相关联的对象不应当被用在where子句(或其它任何子句)中。同时,相关联的对象并不在查询的结果中直接返回,但可以通过他们的父对象来访问到他们。
注意fetch构造变量在使用了scroll()或iterate()函数的查询中是不能使用的。最后注意,使用fulljoinfetch与rightjoinfetch是没有意义的。
如果你使用属性级别的延迟获取(lazyfetching)(这是通过重新编写字节码实现的),可以使用fetchallproperties来强制Hibernate立即取得那些原本需要延迟加载的属性(在第一个查询中)。
fromDocumentfetchallpropertiesorderbyname
fromDocumentdocfetchallpropertieswherelower(doc.name)like'%cats%'
15.4.select子句
select子句选择将哪些对象与属性返回到查询结果集中.考虑如下情况:
selectmate
fromCatascat
innerjoincat.mateasmate
该语句将选择matesofotherCats。(其他猫的配偶)实际上,你可以更简洁的用以下的查询语句表达相同的含义:
selectcat.matefromCatcat
查询语句可以返回值为任何类型的属性,包括返回类型为某种组件(Component)的属性:
selectcat.namefromDomesticCatcat
wherecat.namelike'fri%'
selectcust.name.firstNamefromCustomerascust
查询语句可以返回多个对象和(或)属性,存放在Object[]队列中,
selectmother,offspr,mate.name
fromDomesticCatasmother
innerjoinmother.mateasmate
leftouterjoinmother.kittensasoffspr
或存放在一个List对象中,
selectnewlist(mother,offspr,mate.name)
fromDomesticCatasmother
innerjoinmother.mateasmate
leftouterjoinmother.kittensasoffspr
也可能直接返回一个实际的类型安全的Java对象,
selectnewFamily(mother,mate,offspr)
fromDomesticCatasmother
joinmother.mateasmate
leftjoinmother.kittensasoffspr
假设类Family有一个合适的构造函数.
你可以使用关键字as给“被选择了的表达式”指派别名:
selectmax(bodyWeight)asmax,min(bodyWeight)asmin,count(*)asn
fromCatcat
这种做法在与子句selectnewmap一起使用时最有用:
selectnewmap(max(bodyWeight)asmax,min(bodyWeight)asmin,count(*)asn)
fromCatcat
该查询返回了一个Map的对象,内容是别名与被选择的值组成的名-值映射。
15.5.聚集函数
HQL查询甚至可以返回作用于属性之上的聚集函数的计算结果:
selectavg(cat.weight),sum(cat.weight),max(cat.weight),count(cat)
fromCatcat
受支持的聚集函数如下:
avg(...),sum(...),min(...),max(...)
count(*)
count(...),count(distinct...),count(all...)
你可以在选择子句中使用数学操作符、连接以及经过验证的SQL函数:
selectcat.weight+sum(kitten.weight)
fromCatcat
joincat.kittenskitten
groupbycat.id,cat.weight
selectfirstName||''||initial||''||upper(lastName)fromPerson
关键字distinct与all也可以使用,它们具有与SQL相同的语义.
selectdistinctcat.namefromCatcat
selectcount(distinctcat.name),count(cat)fromCatcat
15.6.多态查询
一个如下的查询语句:
fromCatascat
不仅返回Cat类的实例,也同时返回子类DomesticCat的实例.Hibernate可以在from子句中指定任何Java类或接口.查询会返回继承了该类的所有持久化子类的实例或返回声明了该接口的所有持久化类的实例。下面的查询语句返回所有的被持久化的对象:
fromjava.lang.Objecto
接口Named可能被各种各样的持久化类声明:
fromNamedn,Namedmwheren.name=m.name
注意,最后的两个查询将需要超过一个的SQLSELECT.这表明orderby子句没有对整个结果集进行正确的排序.(这也说明你不能对这样的查询使用Query.scroll()方法.)
15.7.where子句
where子句允许你将返回的实例列表的范围缩小.如果没有指定别名,你可以使用属性名来直接引用属性:
fromCatwherename='Fritz'
如果指派了别名,需要使用完整的属性名:
fromCatascatwherecat.name='Fritz'
返回名为(属性name等于)'Fritz'的Cat类的实例。
selectfoo
fromFoofoo,Barbar
wherefoo.startDate=bar.date
将返回所有满足下面条件的Foo类的实例:存在如下的bar的一个实例,其date属性等于Foo的startDate属性。复合路径表达式使得where子句非常的强大,考虑如下情况:
fromCatcatwherecat.mate.nameisnotnull
该查询将被翻译成为一个含有表连接(内连接)的SQL查询。如果你打算写像这样的查询语句
fromFoofoo
wherefoo.bar.baz.customer.address.cityisnotnull
在SQL中,你为达此目的将需要进行一个四表连接的查询。
=运算符不仅可以被用来比较属性的值,也可以用来比较实例:
fromCatcat,Catrivalwherecat.mate=rival.mate
selectcat,mate
fromCatcat,Catmate
wherecat.mate=mate
特殊属性(小写)id可以用来表示一个对象的唯一的标识符。(你也可以使用该对象的属性名。)
fromCatascatwherecat.id=123
fromCatascatwherecat.mate.id=69
第二个查询是有效的。此时不需要进行表连接!
同样也可以使用复合标识符。比如Person类有一个复合标识符,它由country属性与medicareNumber属性组成。
frombank.Personperson
whereperson.id.country='AU'
andperson.id.medicareNumber=123456
frombank.Accountaccount
whereaccount.owner.id.country='AU'
andaccount.owner.id.medicareNumber=123456
第二个查询也不需要进行表连接。
同样的,特殊属性class在进行多态持久化的情况下被用来存取一个实例的鉴别值(discriminatorvalue)。一个嵌入到where子句中的Java类的名字将被转换为该类的鉴别值。
fromCatcatwherecat.class=DomesticCat
你也可以声明一个属性的类型是组件或者复合用户类型(以及由组件构成的组件等等)。永远不要尝试使用以组件类型来结尾的路径表达式(path-expression)(与此相反,你应当使用组件的一个属性来结尾)。举例来说,如果store.owner含有一个包含了组件的实体address
store.owner.address.city//正确
store.owner.address//错误!
一个“任意”类型有两个特殊的属性id和class,来允许我们按照下面的方式表达一个连接(AuditLog.item是一个属性,该属性被映射为<any>)。
fromAuditLoglog,Paymentpayment
wherelog.item.class='Payment'andlog.item.id=payment.id
注意,在上面的查询与句中,log.item.class和payment.class将涉及到完全不同的数据库中的列。
15.8.表达式
在where子句中允许使用的表达式包括大多数你可以在SQL使用的表达式种类:
数学运算符+,-,*,/
二进制比较运算符=,>=,<=,<>,!=,like
逻辑运算符and,or,not
in,notin,between,isnull,isnotnull,isempty,isnotempty,memberofandnotmemberof
"简单的"case,case...when...then...else...end,和"搜索"case,casewhen...then...else...end
字符串连接符...||...orconcat(...,...)
current_date(),current_time(),current_timestamp()
second(...),minute(...),hour(...),day(...),month(...),year(...),
EJB-QL3.0定义的任何函数或操作:substring(),trim(),lower(),upper(),length(),locate(),abs(),sqrt(),bit_length()
coalesce()和nullif()
cast(...as...),其第二个参数是某Hibernate类型的名字,以及extract(...from...),只要ANSIcast()和extract()被底层数据库支持
任何数据库支持的SQL标量函数,比如sign(),trunc(),rtrim(),sin()
JDBC参数传入?
命名参数:name,:start_date,1
SQL直接常量'foo',69,'1970-01-0110:00:01.0'
Javapublicstaticfinal类型的常量eg.Color.TABBY
关键字in与between可按如下方法使用:
fromDomesticCatcatwherecat.namebetween'A'and'B'
fromDomesticCatcatwherecat.namein('Foo','Bar','Baz')
而且否定的格式也可以如下书写:
fromDomesticCatcatwherecat.namenotbetween'A'and'B'
fromDomesticCatcatwherecat.namenotin('Foo','Bar','Baz')
同样,子句isnull与isnotnull可以被用来测试空值(null).
在Hibernate配置文件中声明HQL“查询替代(querysubstitutions)”之后,布尔表达式(Booleans)可以在其他表达式中轻松的使用:
<propertyname="hibernate.query.substitutions">true1,false0</property>
系统将该HQL转换为SQL语句时,该设置表明将用字符1和0来取代关键字true和false:
fromCatcatwherecat.alive=true
你可以用特殊属性size,或是特殊函数size()测试一个集合的大小。
fromCatcatwherecat.kittens.size>0
fromCatcatwheresize(cat.kittens)>0
对于索引了(有序)的集合,你可以使用minindex与maxindex函数来引用到最小与最大的索引序数。同理,你可以使用minelement与maxelement函数来引用到一个基本数据类型的集合中最小与最大的元素。
fromCalendarcalwheremaxelement(cal.holidays)>currentdate
fromOrderorderwheremaxindex(order.items)>100
fromOrderorderwhereminelement(order.items)>10000
在传递一个集合的索引集或者是元素集(elements与indices函数)或者传递一个子查询的结果的时候,可以使用SQL函数any,some,all,exists,in
selectmotherfromCatasmother,Cataskit
wherekitinelements(foo.kittens)
selectpfromNameListlist,Personp
wherep.name=someelements(list.names)
fromCatcatwhereexistselements(cat.kittens)
fromPlayerpwhere3>allelements(p.scores)
fromShowshowwhere'fizard'inindices(show.acts)
注意,在Hibernate3种,这些结构变量-size,elements,indices,minindex,maxindex,minelement,maxelement-只能在where子句中使用。
一个被索引过的(有序的)集合的元素(arrays,lists,maps)可以在其他索引中被引用(只能在where子句中):
fromOrderorderwhereorder.items[0].id=1234
selectpersonfromPersonperson,Calendarcalendar
wherecalendar.holidays['nationalday']=person.birthDay
andperson.nationality.calendar=calendar
selectitemfromItemitem,Orderorder
whereorder.items[order.deliveredItemIndices[0]]=itemandorder.id=11
selectitemfromItemitem,Orderorder
whereorder.items[maxindex(order.items)]=itemandorder.id=11
在[]中的表达式甚至可以是一个算数表达式。
selectitemfromItemitem,Orderorder
whereorder.items[size(order.items)-1]=item
对于一个一对多的关联(one-to-manyassociation)或是值的集合中的元素,HQL也提供内建的index()函数,
selectitem,index(item)fromOrderorder
joinorder.itemsitem
whereindex(item)<5
如果底层数据库支持标量的SQL函数,它们也可以被使用
fromDomesticCatcatwhereupper(cat.name)like'FRI%'
如果你还不能对所有的这些深信不疑,想想下面的查询。如果使用SQL,语句长度会增长多少,可读性会下降多少:
selectcust
fromProductprod,
Storestore
innerjoinstore.customerscust
whereprod.name='widget'
andstore.location.namein('Melbourne','Sydney')
andprod=allelements(cust.currentOrder.lineItems)
提示:会像如下的语句
SELECTcust.name,cust.address,cust.phone,cust.id,cust.current_order
FROMcustomerscust,
storesstore,
locationsloc,
store_customerssc,
productprod
WHEREprod.name='widget'
ANDstore.loc_id=loc.id
ANDloc.nameIN('Melbourne','Sydney')
ANDsc.store_id=store.id
ANDsc.cust_id=cust.id
ANDprod.id=ALL(
SELECTitem.prod_id
FROMline_itemsitem,orderso
WHEREitem.order_id=o.id
ANDcust.current_order=o.id
)
15.9.orderby子句
查询返回的列表(list)可以按照一个返回的类或组件(components)中的任何属性(property)进行排序:
fromDomesticCatcat
orderbycat.nameasc,cat.weightdesc,cat.birthdate
可选的asc或desc关键字指明了按照升序或降序进行排序.
15.10.groupby子句
一个返回聚集值(aggregatevalues)的查询可以按照一个返回的类或组件(components)中的任何属性(property)进行分组:
selectcat.color,sum(cat.weight),count(cat)
fromCatcat
groupbycat.color
selectfoo.id,avg(name),max(name)
fromFoofoojoinfoo.namesname
groupbyfoo.id
having子句在这里也允许使用.
selectcat.color,sum(cat.weight),count(cat)
fromCatcat
groupbycat.color
havingcat.colorin(eg.Color.TABBY,eg.Color.BLACK)
如果底层的数据库支持的话(例如不能在MySQL中使用),SQL的一般函数与聚集函数也可以出现在having与orderby子句中。
selectcat
fromCatcat
joincat.kittenskitten
groupbycat
havingavg(kitten.weight)>100
orderbycount(kitten)asc,sum(kitten.weight)desc
注意groupby子句与orderby子句中都不能包含算术表达式(arithmeticexpressions).
15.11.子查询
对于支持子查询的数据库,Hibernate支持在查询中使用子查询。一个子查询必须被圆括号包围起来(经常是SQL聚集函数的圆括号)。甚至相互关联的子查询(引用到外部查询中的别名的子查询)也是允许的。
fromCatasfatcat
wherefatcat.weight>(
selectavg(cat.weight)fromDomesticCatcat
)
fromDomesticCatascat
wherecat.name=some(
selectname.nickNamefromNameasname
)
fromCatascat
wherenotexists(
fromCatasmatewheremate.mate=cat
)
fromDomesticCatascat
wherecat.namenotin(
selectname.nickNamefromNameasname
)
在select列表中包含一个表达式以上的子查询,你可以使用一个元组构造符(tupleconstructors):
fromCatascat
wherenot(cat.name,cat.color)in(
selectcat.name,cat.colorfromDomesticCatcat
)
注意在某些数据库中(不包括Oracle与HSQL),你也可以在其他语境中使用元组构造符,比如查询用户类型的组件与组合:
fromPersonwherename=('Gavin','A','King')
该查询等价于更复杂的:
fromPersonwherename.first='Gavin'andname.initial='A'andname.last='King')
有两个很好的理由使你不应当作这样的事情:首先,它不完全适用于各个数据库平台;其次,查询现在依赖于映射文件中属性的顺序。
15.12.HQL示例
Hibernate查询可以非常的强大与复杂。实际上,Hibernate的一个主要卖点就是查询语句的威力。这里有一些例子,它们与我在最近的一个项目中使用的查询非常相似。注意你能用到的大多数查询比这些要简单的多!
下面的查询对于某个特定的客户的所有未支付的账单,在给定给最小总价值的情况下,返回订单的id,条目的数量和总价值,返回值按照总价值的结果进行排序。为了决定价格,查询使用了当前目录。作为转换结果的SQL查询,使用了ORDER,ORDER_LINE,PRODUCT,CATALOG和PRICE库表。
selectorder.id,sum(price.amount),count(item)
fromOrderasorder
joinorder.lineItemsasitem
joinitem.productasproduct,
Catalogascatalog
joincatalog.pricesasprice
whereorder.paid=false
andorder.customer=:customer
andprice.product=product
andcatalog.effectiveDate<sysdate
andcatalog.effectiveDate>=all(
selectcat.effectiveDate
fromCatalogascat
wherecat.effectiveDate<sysdate
)
groupbyorder
havingsum(price.amount)>:minAmount
orderbysum(price.amount)desc
这简直是一个怪物!实际上,在现实生活中,我并不热衷于子查询,所以我的查询语句看起来更像这个:
selectorder.id,sum(price.amount),count(item)
fromOrderasorder
joinorder.lineItemsasitem
joinitem.productasproduct,
Catalogascatalog
joincatalog.pricesasprice
whereorder.paid=false
andorder.customer=:customer
andprice.product=product
andcatalog=:currentCatalog
groupbyorder
havingsum(price.amount)>:minAmount
orderbysum(price.amount)desc
下面一个查询计算每一种状态下的支付的数目,除去所有处于AWAITING_APPROVAL状态的支付,因为在该状态下当前的用户作出了状态的最新改变。该查询被转换成含有两个内连接以及一个相关联的子选择的SQL查询,该查询使用了表PAYMENT,PAYMENT_STATUS以及PAYMENT_STATUS_CHANGE。
selectcount(payment),status.name
fromPaymentaspayment
joinpayment.currentStatusasstatus
joinpayment.statusChangesasstatusChange
wherepayment.status.name<>PaymentStatus.AWAITING_APPROVAL
or(
statusChange.timeStamp=(
selectmax(change.timeStamp)
fromPaymentStatusChangechange
wherechange.payment=payment
)
andstatusChange.user<>:currentUser
)
groupbystatus.name,status.sortOrder
orderbystatus.sortOrder
如果我把statusChanges实例集映射为一个列表(list)而不是一个集合(set),书写查询语句将更加简单.
selectcount(payment),status.name
fromPaymentaspayment
joinpayment.currentStatusasstatus
wherepayment.status.name<>PaymentStatus.AWAITING_APPROVAL
orpayment.statusChanges[maxIndex(payment.statusChanges)].user<>:currentUser
groupbystatus.name,status.sortOrder
orderbystatus.sortOrder
下面一个查询使用了MSSQLServer的isNull()函数用以返回当前用户所属组织的组织帐号及组织未支付的账。它被转换成一个对表ACCOUNT,PAYMENT,PAYMENT_STATUS,ACCOUNT_TYPE,ORGANIZATION以及ORG_USER进行的三个内连接,一个外连接和一个子选择的SQL查询。
selectaccount,payment
fromAccountasaccount
leftouterjoinaccount.paymentsaspayment
where:currentUserinelements(account.holder.users)
andPaymentStatus.UNPAID=isNull(payment.currentStatus.name,PaymentStatus.UNPAID)
orderbyaccount.type.sortOrder,account.accountNumber,payment.dueDate
对于一些数据库,我们需要弃用(相关的)子选择。
selectaccount,payment
fromAccountasaccount
joinaccount.holder.usersasuser
leftouterjoinaccount.paymentsaspayment
where:currentUser=user
andPaymentStatus.UNPAID=isNull(payment.currentStatus.name,PaymentStatus.UNPAID)
orderbyaccount.type.sortOrder,account.accountNumber,payment.dueDate
15.13.批量的UPDATE&DELETE语句
HQL现在支持UPDATE与DELETE语句.查阅第14.3节“大批量更新/删除(Bulkupdate/delete)”以获得更多信息。
15.14.小技巧&小窍门
你可以统计查询结果的数目而不必实际的返回他们:
((Integer)session.iterate("selectcount(*)from....").next()).intValue()
若想根据一个集合的大小来进行排序,可以使用如下的语句:
selectusr.id,usr.name
fromUserasusr
leftjoinusr.messagesasmsg
groupbyusr.id,usr.name
orderbycount(msg)
如果你的数据库支持子选择,你可以在你的查询的where子句中为选择的大小(selectionsize)指定一个条件:
fromUserusrwheresize(usr.messages)>=1
如果你的数据库不支持子选择语句,使用下面的查询:
selectusr.id,usr.name
fromUserusr.name
joinusr.messagesmsg
groupbyusr.id,usr.name
havingcount(msg)>=1
因为内连接(innerjoin)的原因,这个解决方案不能返回含有零个信息的User类的实例,所以这种情况下使用下面的格式将是有帮助的:
selectusr.id,usr.name
fromUserasusr
leftjoinusr.messagesasmsg
groupbyusr.id,usr.name
havingcount(msg)=0
JavaBean的属性可以被绑定到一个命名查询(namedquery)的参数上:
Queryq=s.createQuery("fromfooFooasfoowherefoo.name=:nameandfoo.size=:size");
q.setProperties(fooBean);//fooBean包含方法getName()与getSize()
Listfoos=q.list();
通过将接口Query与一个过滤器(filter)一起使用,集合(Collections)是可以分页的:
Queryq=s.createFilter(collection,"");//一个简单的过滤器
q.setMaxResults(PAGE_SIZE);
q.setFirstResult(PAGE_SIZE*pageNumber);
Listpage=q.list();
通过使用查询过滤器(queryfilter)可以将集合(Collection)的原素分组或排序:
CollectionorderedCollection=s.filter(collection,"orderbythis.amount");
Collectioncounts=s.filter(collection,"selectthis.type,count(this)groupbythis.type");
不用通过初始化,你就可以知道一个集合(Collection)的大小:
((Integer)session.iterate("selectcount(*)from....").next()).intValue();