HQL & JPQL - Part Ⅲ

Joins, reporting queries, and subselects

抓取策略這篇文章中有提到dynamic fetching,用HQL/Criteria來(lái)動(dòng)態(tài)抓取,最終是利用JOIN - 連接查詢。

Joining relations and associations

首先要明白inner join和outer join,inner join返回兩張表中都滿足條件的結(jié)果集,left outer join表示主表數(shù)據(jù)全部返回,但是副表只返回滿足條件的數(shù)據(jù),如果主表數(shù)據(jù)在副表中沒(méi)有,副表字段用null代替。

如下兩張表



inner join的結(jié)果,注意ID為3的item沒(méi)有返回:



left join的結(jié)果:

HQL and JPQL join options

HQL/JPQL提供了四種JOIN的表達(dá)方式:

  • An implicit association join
  • An ordinary join in the FROM clause
  • A fetch join in the FROM cluase
  • A theta-style join in the WHERE clause

Implicit association joins

隱式連接,使用點(diǎn)操作符來(lái)完成。在HQL/JPQL中點(diǎn)操作符的兩種主要用途:

  • Querying components - 查詢組件
  • Expressing implicit association joins - 表達(dá)隱式連接
// 查詢組件,此時(shí)不會(huì)有表關(guān)聯(lián)
// Address類屬性對(duì)應(yīng)的字段都在User表中
from User u where u.homeAddress.city = 'Bangkok'

// 放在SELECT子句中
select distinct u.homeAddress.city from User u

Implicit association join:

// 從BID到ITEM是many-to-one的關(guān)系
from Bid bid where bid.item.description like '%Foo%'

Implicit joins are always directed along many-to-one or one-to-one associations, never through a collection-valued association (you can’t write item.bids.amount).

隱式連接總是從many指向one或從one指向one來(lái)引用,永遠(yuǎn)不要從one指向many.

多張表隱式連接:

// 這里假設(shè)從ITEM到CATEGORY是多對(duì)一關(guān)系
from Bid bid where bid.item.category.name like 'Laptop%' and bid.item.successfulBid.amount > 100

雖然這樣寫可以,但是要一下子看明白到底關(guān)聯(lián)了幾張表還是很費(fèi)勁,不夠簡(jiǎn)單直白。
如果需要多張表關(guān)聯(lián),還是不要使用隱式連接。

select ...
from BID B
inner join ITEM I on B.ITEM_ID = I.ITEM_ID
inner join CATEGORY C on I.CATEGORY_ID = C.CATEGORY_ID
inner join BID SB on I.SUCCESSFUL_BID_ID = SB.BID_ID
where C.NAME like 'Laptop%'
and SB.AMOUNT > 100

Joins expressed in the FROM clause

在HQL/JPQL的FROM子句中使用JOIN,首先來(lái)看inner join:

// 在FROM子句中使用JOIN,為關(guān)聯(lián)集合設(shè)置別名
// join默認(rèn)為inner join
from Item i join i.bids b where i.description like '%Foo%' and b.amount > 100

生成的SQL如下:

select i.DESCRIPTION, i.INITIAL_PRICE, ...
    b.BID_ID, b.AMOUNT, b.ITEM_ID, b.CREATED_ON
from ITEM i
inner join BID b on i.ITEM_ID = b.ITEM_ID
where i.DESCRIPTION like '%Foo%' and b.AMOUNT > 100

獲取查詢結(jié)果代碼如下:

Query q = session.createQuery("from Item i join i.bids b");
Iterator pairs = q.list().iterator();
while (pairs.hasNext()) {
    Object[] pair = (Object[]) pairs.next();
    Item item = (Item) pair[0];
    Bid bid = (Bid) pair[1];
}

返回List中的元素是對(duì)象數(shù)組,根據(jù)HQL中對(duì)象的順序,第一個(gè)是Item第二個(gè)是Bid。
這里要注意,從ITEM到BID是一對(duì)多的關(guān)系,所以查詢inner join的查詢結(jié)果中,很可能存在重復(fù)的ITEM行,但是在Hibernate中只是重復(fù)的對(duì)象引用,不是重復(fù)的Item對(duì)象。

A particular Item may appear multiple times, once for each associated Bid. These duplicate items are duplicate in-memory references, not duplicate instances!

如果只想獲取ITEM表數(shù)據(jù):

// 添加SELECT子句來(lái)指定要查詢的對(duì)象
select i from Item i join i.bids b where i.description like '%Foo%' and b.amount > 100

獲取查詢結(jié)果代碼如下:

Query q = session.createQuery("select i from Item i join i.bids b");
Iterator items = q.list().iterator();
while ( items.hasNext() ) {
    Item item = (Item) items.next();
}

以上都是inner join,left join通常用于動(dòng)態(tài)抓取策略(Dynamic fetch strategy),動(dòng)態(tài)抓取下節(jié)會(huì)講。
下面看個(gè)使用left join,但沒(méi)有使用動(dòng)態(tài)抓取策略:

// 注意with關(guān)鍵字
from Item i left join i.bids b with b.amount > 100 where i.description like '%Foo%'

首先要知道LEFT JOINLEFT OUTER JOIN是一回事,通常不使用RIGHT JOIN。此條HQL仍然返回元素是對(duì)象數(shù)組的List,數(shù)組中第一個(gè)元素是Item,第二個(gè)是Bid。
注意with條件,如果將b.amount > 100放到WHERE子句中,則是對(duì)LEFT JOIN之后的集合做過(guò)濾,所以沒(méi)有BID的ITEM就被過(guò)濾掉了;如果是放在JOIN之后,則只過(guò)濾BID,此時(shí)LEFT JOIN,沒(méi)有BID的ITEM記錄最終仍然會(huì)被查詢出來(lái)。

Dynamic fetching strategies with joins

Hibernate默認(rèn)是fetch on demand的抓取策略,所以默認(rèn)會(huì)為關(guān)聯(lián)實(shí)體創(chuàng)建代理,為集合創(chuàng)建collection wrapper,只有當(dāng)真正需要時(shí)才去初始化;上一節(jié)的所有查詢?nèi)绻褂媚J(rèn)的抓取策略,則關(guān)聯(lián)屬性和collection都不會(huì)真正初始化。

你可以修改全局的抓取策略來(lái)改變默認(rèn)行為;還可以保持默認(rèn)行為,通過(guò)Dynamic fetching strategy為某些CASE提供eager fetch。

HQL/JPQL中可以通過(guò)FETCH關(guān)鍵字來(lái)實(shí)現(xiàn)eager fetch:

from Item i left join fetch i.bids where i.description like '%Foo%'

上面HQL返回元素是Item對(duì)象的List,并且其bids集合屬性已經(jīng)被初始化。
生成的SQL如下:

select i.DESCRIPTION, i.INITIAL_PRICE, ...
        b.BID_ID, b.AMOUNT, b.ITEM_ID, b.CREATED_ON
from ITEM i
left outer join BID b on i.ITEM_ID = b.ITEM_ID
where i.DESCRIPTION like '%Foo%'

上面HQL中,從Item到Bid是one-to-many,對(duì)于many-to-one或one-to-one關(guān)聯(lián)關(guān)系,也可以使用FETCH關(guān)鍵字:

from Bid bid
    left join fetch bid.item
    left join fetch bid.bidder where bid.amount > 100

生成的SQL如下:

select b.BID_ID, b.AMOUNT, b.ITEM_ID, b.CREATED_ON
        i.DESCRIPTION, i.INITIAL_PRICE, ...
        u.USERNAME, u.FIRSTNAME, u.LASTNAME, ...
from BID b
left outer join ITEM i on i.ITEM_ID = b.ITEM_ID
left outer join USER u on u.USER_ID = b.BIDDER_ID
where b.AMOUNT > 100

HQL/JPQL注意事項(xiàng):

  • You never assign an alias to any fetch-joined association or collection for further restriction or projection. So left join fetch i.bids b where b =...is invalid, whereas left join fetch i.bids b join fetch b.bidder is valid.
  • You shouldn’t fetch more than one collection in parallel; otherwise you create a Cartesian product. You can fetch as many single-valued associated objects as you like without creating a product.
  • HQL and JPA QL ignore any fetching strategy you’ve defined in mapping metadata. For example, mapping the bids collection in XML with fetch="join", has no effect on any HQL or JPA QL statement.
  • If you eager-fetch a collection, duplicates may be returned. Look at figure 14.3: This is exactly the SQL operation that is executed for a select i from Item i join fetch i.bids HQL or JPA QL query. Each Item is duplicated on the left side of the result table as many times as related Bid data is present. The List returned by the HQL or JPA QL query preserves these duplicates as references. If you prefer to filter out these duplicates you need to either wrap the List in a Set (for example, with Set noDupes = new LinkedHashSet(resultList)) or use the DISTINCT keyword: select distinct i from Item i join fetch i.bidsnote that in this case the DISTINCT doesn’t operate at the SQL level, but forces Hibernate to filter out duplicates in memory when marshaling the result into objects. Clearly, duplicates can’t be avoided in the SQL result.
  • Query execution options that are based on the SQL result rows, such as pagination with setMaxResults()/setFirstResult(), are semantically incorrect if a collection is eagerly fetched. If you have an eager fetched collection in your query, at the time of writing, Hibernate falls back to limiting the result in-memory, instead of using SQL. This may be less efficient, so we don’t recommend the use of JOIN FETCH with setMaxResults()/setFirstResult(). Future versions of Hibernate may fall back to a different SQL query strategy (such as two queries and subselect fetching) if setMaxResults()/setFirstResult() is used in combination with a JOIN FETCH.

Theta-style joins

from User user, LogRecord log where user.username = log.username

這就是Theta-style join,兩張表的關(guān)聯(lián)條件在WHERE子句中指定。在HQL/JPQL中,當(dāng)兩個(gè)實(shí)體沒(méi)有做外鍵關(guān)聯(lián)時(shí),可以使用Theta-style join,最終是inner join的結(jié)果。

查詢結(jié)果取值:

Iterator i = session.createQuery("from User user, LogRecord log where user.username = log.username").list().iterator();
while (i.hasNext()) {
    Object[] pair = (Object[]) i.next();
    User user = (User) pair[0];
    LogRecord log = (LogRecord) pair[1];
}

Comparing identifiers

HQL/JPQL比較對(duì)象引用:

from Item i, User u where i.seller = u and u.username = 'steve'

與此theta-style join等價(jià)的from clause join:

from Item i join i.seller u where u.username = 'steve'

與以上兩條HQL等價(jià)的比較主鍵(標(biāo)識(shí)符)方式:

// 比較主鍵
from Item i, User u where i.seller.id = u.id and u.username = 'steve'

HQL guarantees that id always refers to any arbitrarily named identifier property; JPA QL doesn’t. HQL中id總是指向?qū)嶓w的主鍵屬性,不管其主鍵屬性名稱是什么。

下面這條theta-style join沒(méi)有等價(jià)的from clause join:

// i.seller和b.bidder都外鍵與USER表
from Item i, Bid b where i.seller = b.bidder

但是有等價(jià)的比較主鍵方式:

from Item i, Bid b where i.seller.id = b.bidder.id

主鍵做為查詢參數(shù):

Long userId = ...
Query q = session.createQuery("from Comment c where c.fromUser.id = :userId");
q.setLong("userId", userId);
List result = q.list();

下面兩條HQL,第一條沒(méi)有表連接;第二條最終會(huì)生成表連接。

// no joins at all
from Bid b where b.item.id = 1

// use a implicit table join
from Bid b where b.item.description like '%Foo%'

Reporting queries

利用group by和聚合函數(shù)來(lái)查詢數(shù)據(jù),稱為reporting query.
報(bào)表查詢出的不是實(shí)體對(duì)象,所以Hibernate不用為其查詢結(jié)果維護(hù)狀態(tài)(翻譯的不好,原文如下)。

You don’t need transactional entity instances and can save the overhead of automatic dirty checking and caching in the persistence context.

Projection with aggregation functions

HQL/JPQL識(shí)別的聚合函數(shù)有:count(),min(),max(),sum(),avg()。當(dāng)不使用GROUP BY時(shí),SELECT子句中使用了聚合函數(shù),則SELECT子句中只能有聚合函數(shù),不同有其他字段

Long count = (Long) session.createQuery("select count(i) from Item i").uniqueResult();
// 統(tǒng)計(jì)successfulBid屬性不為null的數(shù)量,為null的被忽略
select count(i.successfulBid) from Item i

// 忽略重復(fù)的
select count(distinct i.description) from Item i
// 因?yàn)閍mount屬性是BigDecimal類型,所以SUM返回的也是BigDecimal類型
// 這里使用了implict join,從Item到Bid
select sum(i.successfulBid.amount) from Item i
// 返回對(duì)象數(shù)組Object[],包含兩個(gè)BigDecimal對(duì)象
select min(bid.amount), max(bid.amount) from Bid bid where bid.item.id = 1

Grouping aggregated results

group by分組:

select u.lastname, count(u) from User u group by u.lastname

生成如下SQL:

// 生成的SQL自動(dòng)count實(shí)體類的主鍵
select u.LAST_NAME, count(u.USER_ID) from USER u group by u.LAST_NAME
// implicit join
select bid.item.id, count(bid), avg(bid.amount) from Bid bid where bid.item.successfulBid is null group by bid.item.id

// 等價(jià)的from clause join
select bidItem.id, count(bid), avg(bid.amount)
    from Bid bid join bid.item bidItem 
    where bidItem.successfulBid is null group by bidItem.id

Restricting groups with having

使用HAVING子句:

select user.lastname, count(user) from User user group by user.lastname having user.lastname like 'A%'

select item.id, count(bid), avg(bid.amount)
    from Item item join item.bids bid
    where item.successfulBid is null group by item.id having count(bid) > 10

Hibernate將查詢結(jié)果的每一行生成一個(gè)對(duì)象數(shù)組Object[]。

Utilizing dynamic instantiation

動(dòng)態(tài)實(shí)例化:

// 將查詢結(jié)果直接封裝成ItemBidSummary對(duì)象
select new ItemBidSummary(
    bid.item.id, count(bid), avg(bid.amount)
)
from Bid bid where bid.item.successfulBid is null group by bid.item.id

返回的ItemBidSummary對(duì)象是transient state;ItemBidSummary類名要含包名。

Improving performance with report queries

Report queries using projection in HQL and JPA QL let you specify which properties you wish to retrieve. For report queries, you aren’t selecting entities in managed state, but only properties or aggregated values.

This query doesn’t return persistent entity instances, so Hibernate doesn’t add any persistent object to the persistence context cache. This means that no object must be watched for dirty state either.

由于report query不是查詢整個(gè)實(shí)體,所以Hibernate不用維護(hù)其查詢結(jié)果的狀態(tài),所以更加高效。

Using subselects

HQL/JPQL不支持SELECT子句的子查詢,支持WHERE子句的子查詢。

Correlated and uncorrelated nesting

相關(guān)子查詢 - correlated subquery

from User u where 10 < (
    select count(i) from u.items i where i.successfulBid is not null
)

uncorrelated subquery:

// 查詢出所有和最高amount相差在1塊之內(nèi)的bid
from Bid bid where bid.amount + 1 >= (
    select max(b.amount) from Bid b
)

Quantification

量詞關(guān)鍵字:ALL, ANY/SOME, IN:

// 返回這些Item:Item對(duì)應(yīng)的所有Bid的amount都小于100
from Item i where 100 > all ( select b.amount from i.bids b )

// 返回這些Item: Item對(duì)應(yīng)的所有Bid的amount只要有一個(gè)大于等于100
from Item i where 100 <= any ( select b.amount from i.bids b )

// 返回這些Item: Item對(duì)應(yīng)的所有Bid的amount只要有一個(gè)等于100
from Item i where 100 = some ( select b.amount from i.bids b )

// 返回這些Item: Item對(duì)應(yīng)的所有Bid的amount只有有一個(gè)等于100(同上)
from Item i where 100 in ( select b.amount from i.bids b )

此文是對(duì)《Java Persistence with Hibernate》第14章第三部分的歸納。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容