Hibernate.org社区文档

第八章。JP-QL:对象查询语言

8.1. 大小写敏感性
8.2. from 子句
8.3. 关联和联接
8.4. select 子句
8.5. 聚合函数
8.6. 多态查询
8.7. where 子句
8.8. 表达式
8.9. order by 子句
8.10. group by 子句
8.11. 子查询
8.12. JP-QL 示例
8.13. 批量 UPDATE 和 DELETE 语句
8.14. 窍门和技巧

Java 持久性查询语言 (JP-QL) 深受 HQL(Hibernate 本机查询语言)的启发。因此,两者都非常接近于 SQL,但却是可移植的且独立于数据库模式。熟悉 HQL 的人使用 JP-QL 应该不会有任何问题。事实上,HQL 是 JP-QL 的严格超集,并且你可以对两种类型的查询使用相同的查询 API。然而,可移植 JPA 应用程序应该坚持使用 JP-QL。

注意

我们强烈建议你使用 Criteria 查询采用使类型安全的查询方法,请参阅 第九章,Criteria 查询

查询不区分大小写,Java 类和属性的名称除外。因此,SeLeCTsELEct 相同,与 SELECT 相同,但 org.hibernate.eg.FOO 不是 org.hibernate.eg.Foofoo.barSet 不是 foo.BARSET

本手册使用小写的 JP-QL 关键字。某些用户觉得查询在大写关键字的情况更可读,但我们觉得嵌入 Java 代码时这种惯例不美观。

最简单的可能 JP-QL 查询采用如下形式

select c from eg.Cat c

它简单地返回 eg.Cat 类的所有实例。与 HQL 不同,select 子句在 JP-QL 中不是可选的。我们通常不需要限定类名,因为实体名称默认为未限定的类名 (@Entity)。所以,我们几乎总是只写

select c from Cat c

你可能已经注意到,你可以向类分配别名,as 关键字是可选的。别名允许你在查询的其他部分中引用 Cat

select cat from Cat as cat

多个类可能会出现,形成笛卡尔积或“交叉”联接。

select from, param from Formula as form, Parameter as param

使用小写字母作为查询别名的首字母被认为是良好的做法,符合 Java 本地变量的命名标准(例如 domesticCat)。

你还可以使用 join向关联实体或甚至是值集合的元素分配别名。

select cat, mate, kitten from Cat as cat
    inner join cat.mate as mate
    left outer join cat.kittens as kitten
select cat from Cat as cat left join cat.mate.kittens as kittens

支持的联接类型从 ANSI SQL 借用

inner joinleft outer join 构造可以简化。

select cat, mate, kitten from Cat as cat
    join cat.mate as mate
    left join cat.kittens as kitten

此外,“获取”连接允许关联或值集合随其父对象通过单一选择初始化。这在集合的情况下尤其有用。它有效地覆盖了关联和集合映射元数据中的获取选项。有关更多信息,请参阅 Hibernate 参考指南的性能章节。

select cat from Cat as cat
    inner join fetch cat.mate
    left join fetch cat.kittens

获取连接通常不需要分配别名,因为关联对象不应在where 子句(或任何其他子句)中使用。此外,关联对象也不会直接在查询结果中返回。相反,它们可以通过父对象进行访问。我们可能需要别名的唯一原因是,如果我们要递归连接获取另一个集合

select cat from Cat as cat 
    inner join fetch cat.mate
    left join fetch cat.kittens child
    left join fetch child.kittens

请注意,无法在使用 scroll()iterate() 调用的查询中使用 fetch 构造。也不应将 fetchsetMaxResults()setFirstResult() 一起使用。可以通过在查询中连接获取多个集合来创建笛卡尔积(如上例所示),请注意,此积的结果不要大于预期。连接获取多个集合角色会为包映射提供意外的结果,因为 Hibernate 无法区分给定包的合法副本与由多表笛卡尔积创建的人工副本。

如果使用属性级延迟获取(带有字节码工具),可以使用 fetch all properties 强制 Hibernate 立即(在第一个查询中)获取延迟属性。这是 Hibernate 特有的选项

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

select 子句选择在查询结果集中返回的对象和属性。请考虑

select mate 
from Cat as cat 
    inner join cat.mate as mate

查询将选择其他 Catmate。实际上,您可以更简洁地表示此查询为

select cat.mate from Cat cat

查询可以返回任何值类型的属性,包括组件类型的属性

select cat.name from DomesticCat cat
where cat.name like 'fri%'
select cust.name.firstName from Customer as cust

查询可以将多个对象和/或属性作为 Object[] 类型的数组返回,

select mother, offspr, mate.name 
from DomesticCat as mother
    inner join mother.mate as mate
    left outer join mother.kittens as offspr

或作为 List(HQL 特有功能)

select new list(mother, offspr, mate.name)
from DomesticCat as mother
    inner join mother.mate as mate
    left outer join mother.kittens as offspr

或作为实际类型安全的 Java 对象(通常称为视图对象),

select new Family(mother, mate, offspr)
from DomesticCat as mother
    join mother.mate as mate
    left join mother.kittens as offspr

假设类 Family 有一个适当的构造函数。

您可以使用 as 将别名分配给所选表达式

select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n
from Cat cat

当与 select new map(HQL 特有功能)一起使用时,它最有用

select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )
from Cat cat

此查询返回从别名到所选值的 Map

HQL 查询甚至可以返回对属性聚合函数的结果

select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)
from Cat cat

支持的聚合函数是

您可以在 select 子句中使用算术运算符、连接和公认的 SQL 函数(取决于配置的方言,这是 HQL 特有的功能)

select cat.weight + sum(kitten.weight) 
from Cat cat 
    join cat.kittens kitten
group by cat.id, cat.weight
select firstName||' '||initial||' '||upper(lastName) from Person

关键字 distinctall 可用于 SQL 中的含义相同。

select distinct cat.name from Cat cat

select count(distinct cat.name), count(cat) from Cat cat

类似这样的查询

select cat from Cat as cat

返回的实例不仅有 Cat,还有像 DomesticCat 这样的子类。Hibernate 查询可以在 from 子句中命名 任意 Java 类或接口(可移植 JP-QL 查询只能命名映射实体)。此查询将返回扩展该类或实现该接口的所有持久类。以下查询将返回所有持久对象

from java.lang.Object o // HQL only

接口 Named 可能会由各种持久类实现

from Named n, Named m where n.name = m.name // HQL only

请注意,后两个查询需要多于一条 SQL SELECT。这意味着 order by 子句并未按正确的顺序排列整个结果集。(这也意味着您无法使用 Query.scroll() 调用这些查询。)

使用 where 子句可以缩小返回的实例列表。如果不存在别名,可以通过名称来引用属性

select cat from Cat cat where cat.name='Fritz'

返回名为 'Fritz' 的 Cat 实例。

select foo 
from Foo foo, Bar bar
where foo.startDate = bar.date

将返回 Foo 的所有实例,其中存在 Bar 的一个实例,其 date 属性等于 FoostartDate 属性。复合路径表达式使 where 子句极其强大。请考虑

select cat from Cat cat where cat.mate.name is not null

此查询转换成一个含有表(内部)联接的 SQL 查询。如果您写类似这样的内容

select foo from Foo foo  
where foo.bar.baz.customer.address.city is not null

您最终会得到一个需要在 SQL 中进行四次表联接的查询。

运算符 = 不仅可用于比较属性,还可用于比较实例

select cat, rival from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate 
from Cat cat, Cat mate
where cat.mate = mate

特殊属性(小写)id 可用于引用对象的唯一标识符。(您还可以使用它映射的标识符属性名称)。请注意,此关键字是 HQL 特有的。

select cat from Cat as cat where cat.id = 123

select cat from Cat as cat where cat.mate.id = 69

第二个查询是有效的。不需要表联接!

也可以使用复合标识符的属性。假设 Person 有一个由 countrymedicareNumber 组成的复合标识符。

select person from bank.Person person
where person.id.country = 'AU' 
    and person.id.medicareNumber = 123456
select account from bank.Account account
where account.owner.id.country = 'AU' 
    and account.owner.id.medicareNumber = 123456

同样,第二个查询不需要表联接。

同样,特殊属性 class 访问的是差异持久情况下的实例的差异值。嵌入在 where 子句中的 Java 类名将转换成它的差异值。同样,这也是 HQL 特有的。

select cat from Cat cat where cat.class = DomesticCat

您也可以指定组件或组合用户类型(以及组件的组件等)的属性。切勿尝试使用以组件类型的属性结尾的路径表达式(与组件的属性相对)。例如,如果 store.owner 是具有组件 address 的实体

store.owner.address.city    // okay
store.owner.address         // error!

“任何”类型具有特殊属性 idclass,允许我们以下述方式表示联接(其中 AuditLog.item 是使用 <any> 映射的属性)。Any 适用于 Hibernate

from AuditLog log, Payment payment 
where log.item.class = 'Payment' and log.item.id = payment.id

请注意,在上面的查询中,log.item.classpayment.class 将引用完全不同的数据库列的值。

where 子句中允许的表达式包括您可以在 SQL 中编写的各种类型的事物

inbetween 可按如下方式使用

select cat from DomesticCat cat where cat.name between 'A' and 'B'
select cat from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )

以及否定形式可按如下方式编写

select cat from DomesticCat cat where cat.name not between 'A' and 'B'
select cat from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )

同样,is nullis not null 可用于测试空值。

布尔值可通过在 Hibernate 配置中声明 HQL 查询替换来在表达式中轻松使用

hibernate.query.substitutions true 1, false 0

这将从这个 HQL 翻译的 SQL 中用文字 10 替换关键词 truefalse

select cat from Cat cat where cat.alive = true

你可以使用特殊属性 size 或特殊 size() 函数(HQL 特定功能)测试集合的大小。

select cat from Cat cat where cat.kittens.size > 0
select cat from Cat cat where size(cat.kittens) > 0

对于编址集合,你可以使用 minindexmaxindex 函数引用最小的和最大的索引。类似地,你可以使用 minelementmaxelement 函数引用基本类型集合的最小元素和最大元素。这些是 HQL 特定功能。

select cal from Calendar cal where maxelement(cal.holidays) > current date
select order from Order order where maxindex(order.items) > 100
select order from Order order where minelement(order.items) > 10000

当传递集合的元素或索引集合(elementsindices 函数)或子查询结果(见下文)时,SQL 函数 any, some, all, exists, in 是可支持的。JP-QL 支持子查询,而 elementsindices 是 HQL 特定功能。

select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
select p from NameList list, Person p
where p.name = some elements(list.names)
select cat from Cat cat where exists elements(cat.kittens)
select cat from Player p where 3 > all elements(p.scores)
select cat from Show show where 'fizard' in indices(show.acts)

注意,这些结构 - size, elements, indices, minindex, maxindex, minelement, maxelement - 只能在 Hibernate 中的 where 从句中使用。

JP-QL 允许你使用 KEY()VALUE() 操作访问 map 的键或值(甚至可以使用 ENTRY() 访问 Entry 对象)。

SELECT i.name, VALUE(p) FROM Item i JOIN i.photos p WHERE KEY(p) LIKE ‘%egret’

在 HQL 中,可以通过索引引用编址集合(数组、列表、map)的元素(仅在 where 从句中)。

select order from Order order where order.items[0].id = 1234
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
    and person.nationality.calendar = calendar
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11

[] 中的表达式甚至可以是一个算术表达式。

select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item

HQL 还提供内置的 index() 函数,用于一对多关联的元素或值集合。

select item, index(item) from Order order 
    join order.items item
where index(item) < 5

可以使用底层数据库支持的标量 SQL 函数。

select cat from DomesticCat cat where upper(cat.name) like 'FRI%'

如果你还不能理解这一切,想想下面这个查询在 SQL 中会多长且多难读

select cust
from Product prod,
    Store store
    inner join store.customers cust
where prod.name = 'widget'
    and store.location.name in ( 'Melbourne', 'Sydney' )
    and prod = all elements(cust.currentOrder.lineItems)

提示:如下

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
    stores store,
    locations loc,
    store_customers sc,
    product prod
WHERE prod.name = 'widget'
    AND store.loc_id = loc.id
    AND loc.name IN ( 'Melbourne', 'Sydney' )
    AND sc.store_id = store.id
    AND sc.cust_id = cust.id
    AND prod.id = ALL(
        SELECT item.prod_id
        FROM line_items item, orders o
        WHERE item.order_id = o.id
            AND cust.current_order = o.id
    )

查询返回的列表可以按返回类的任意属性或组件进行排序

select cat from DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate

可选的 ascdesc 分别表示升序或降序。

返回聚合值的查询可以按返回类的任意属性或组件进行分组

select cat.color, sum(cat.weight), count(cat) 
from Cat cat
group by cat.color
select foo.id, avg(name), max(name) 
from Foo foo join foo.names name
group by foo.id

同样允许使用 having 子句。

select cat.color, sum(cat.weight), count(cat) 
from Cat cat
group by cat.color 
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

SQL 函数和聚合函数可以在 havingorder by 子句中使用,如果由底层数据库支持(例如不在 MySQL 中)。

select cat
from Cat cat
    join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc

请注意, group by 子句和 order by 子句都不可以包含算术表达式。

对于支持子查询的数据库,JP-QL 在查询中支持子查询。子查询必须被括号包围(通常由 SQL 聚合函数调用包围)。甚至允许使用相关子查询(引用外部查询中别名的子查询)。

select fatcat from Cat as fatcat 
where fatcat.weight > ( 
    select avg(cat.weight) from DomesticCat cat 
)
select cat from DomesticCat as cat 
where cat.name = some ( 
    select name.nickName from Name as name 
)
select cat from Cat as cat 
where not exists ( 
    from Cat as mate where mate.mate = cat 
)
select cat from DomesticCat as cat 
where cat.name not in ( 
    select name.nickName from Name as name 
)

对于 select 列表中有多个表达式的子查询,可以使用元组构造函数

select cat from Cat as cat 
where not ( cat.name, cat.color ) in ( 
    select cat.name, cat.color from DomesticCat cat 
)

请注意,在某些数据库(但不是 Oracle 或 HSQLDB)中,可以在其他上下文中使用元组构造函数,例如查询组件或复合用户类型时

select cat from Person where name = ('Gavin', 'A', 'King')

这等效于更详细的

select cat from Person where name.first = 'Gavin' and name.initial = 'A' and name.last = 'King')

有充分的理由让你不会想要做这种事情:首先,它并非完全可移植于不同的数据库平台;其次,该查询现在依赖于映射文档中属性的排序。

Hibernate 查询可以非常强大且复杂。事实上,查询语言的强大功能是 Hibernate 的主要卖点之一(现在是 JP-QL)。以下是一些示例查询,与我在最近的一个项目中使用的查询非常相似。请注意,编写的大多数查询都比这些简单得多!

以下查询返回给定最低总价值的特定客户的所有未付款订单的订单 ID、商品数量和总价值,按总价值对结果进行排序。在确定价格时,它使用当前目录。针对 ORDERORDER_LINEPRODUCTCATALOGPRICE 表的结果 SQL 查询具有四个内联连接和一个(非相关)子查询。

select order.id, sum(price.amount), count(item)
from Order as order
    join order.lineItems as item
    join item.product as product,
    Catalog as catalog
    join catalog.prices as price
where order.paid = false
    and order.customer = :customer
    and price.product = product
    and catalog.effectiveDate < sysdate
    and catalog.effectiveDate >= all (
        select cat.effectiveDate 
        from Catalog as cat
        where cat.effectiveDate < sysdate
    )
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc

简直是怪物!实际上,在现实生活中,我对子查询不是很热衷,所以我的查询实际上更像是这样

select order.id, sum(price.amount), count(item)
from Order as order
    join order.lineItems as item
    join item.product as product,
    Catalog as catalog
    join catalog.prices as price
where order.paid = false
    and order.customer = :customer
    and price.product = product
    and catalog = :currentCatalog
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc

下一个查询计算每个状态的付款数量,排除 AWAITING_APPROVAL 状态中的所有付款,其中最近的状态更改是由当前用户进行的。它转换为一个带有两个内联连接和一个相关子查询的 SQL 查询,针对 PAYMENTPAYMENT_STATUSPAYMENT_STATUS_CHANGE 表。

select count(payment), status.name 
from Payment as payment 
    join payment.currentStatus as status
    join payment.statusChanges as statusChange
where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
    or (
        statusChange.timeStamp = ( 
            select max(change.timeStamp) 
            from PaymentStatusChange change 
            where change.payment = payment
        )
        and statusChange.user <> :currentUser
    )
group by status.name, status.sortOrder
order by status.sortOrder

如果我将 statusChanges 映射为列表而不是集合,则可以更轻松地编写查询。

select count(payment), status.name 
from Payment as payment
    join payment.currentStatus as status
where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
    or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser
group by status.name, status.sortOrder
order by status.sortOrder

但是该查询将特定于 HQL。

下一个查询使用 MS SQL Server isNull() 函数返回当前用户所属组织的所有帐户和未付款,它可转换为具有三个内部连接、一个外部连接和针对 ACCOUNTPAYMENTPAYMENT_STATUSACCOUNT_TYPEORGANIZATIONORG_USER 表的子查询的 SQL 查询。

select account, payment
from Account as account
    join account.holder.users as user
    left outer join account.payments as payment
where :currentUser = user
    and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)
order by account.type.sortOrder, account.accountNumber, payment.dueDate

Hibernate 现在在 HQL/JP-QL 中支持 UPDATE 和 DELETE 语句。有关详细信息,请参阅 第 7.1 节“批量更新/删除”。

若要按集合大小对结果进行排序,请使用以下查询

select usr.id, usr.name
from User as usr 
    left join usr.messages as msg
group by usr.id, usr.name
order by count(msg)

如果您的数据库支持子选择,则可以在查询的 where 子句中指定选择大小的条件

from User usr where size(usr.messages) >= 1

如果您的数据库不支持子选择,请使用以下查询

select usr.id, usr.name
from User usr.name
    join usr.messages msg
group by usr.id, usr.name
having count(msg) >= 1

由于内部连接,此解决方案无法返回没有零个消息的 User,因此以下形式也十分有用

select usr.id, usr.name
from User as usr
    left join usr.messages as msg
group by usr.id, usr.name
having count(msg) = 0