HQL & JPQL - Part Ⅲ

Joins, reporting queries, and subselects

抓取策略這篇文章中有提到dynamic fetching缩焦,用HQL/Criteria來動(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ù)在副表中沒有,副表字段用null代替湾笛。

如下兩張表



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



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)操作符來完成嚎研。在HQL/JPQL中點(diǎn)操作符的兩種主要用途:

  • Querying components - 查詢組件
  • Expressing implicit association joins - 表達(dá)隱式連接
// 查詢組件蓖墅,此時(shí)不會(huì)有表關(guān)聯(lián)
// Address類屬性對應(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來引用,永遠(yuǎn)不要從one指向many.

多張表隱式連接:

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

雖然這樣寫可以临扮,但是要一下子看明白到底關(guān)聯(lián)了幾張表還是很費(fèi)勁论矾,不夠簡單直白。
如果需要多張表關(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,首先來看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中的元素是對象數(shù)組闰靴,根據(jù)HQL中對象的順序彪笼,第一個(gè)是Item第二個(gè)是Bid。
這里要注意传黄,從ITEM到BID是一對多的關(guān)系杰扫,所以查詢inner join的查詢結(jié)果中,很可能存在重復(fù)的ITEM行膘掰,但是在Hibernate中只是重復(fù)的對象引用章姓,不是重復(fù)的Item對象。

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子句來指定要查詢的對象
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窒舟,但沒有使用動(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仍然返回元素是對象數(shù)組的List惠豺,數(shù)組中第一個(gè)元素是Item银还,第二個(gè)是Bid。
注意with條件洁墙,如果將b.amount > 100放到WHERE子句中蛹疯,則是對LEFT JOIN之后的集合做過濾,所以沒有BID的ITEM就被過濾掉了热监;如果是放在JOIN之后捺弦,則只過濾BID,此時(shí)LEFT JOIN孝扛,沒有BID的ITEM記錄最終仍然會(huì)被查詢出來列吼。

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ì)真正初始化理郑。

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

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

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

上面HQL返回元素是Item對象的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,對于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í)體沒有做外鍵關(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比較對象引用:

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沒有等價(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,第一條沒有表連接千所;第二條最終會(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ù)來查詢數(shù)據(jù),稱為reporting query.
報(bào)表查詢出的不是實(shí)體對象淫痰,所以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
// 返回對象數(shù)組Object[]胎挎,包含兩個(gè)BigDecimal對象
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è)對象數(shù)組Object[]沟启。

Utilizing dynamic instantiation

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

// 將查詢結(jié)果直接封裝成ItemBidSummary對象
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對象是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對應(yīng)的所有Bid的amount都小于100
from Item i where 100 > all ( select b.amount from i.bids b )

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肌毅,一起剝皮案震驚了整個(gè)濱河市筷转,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悬而,老刑警劉巖呜舒,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笨奠,居然都是意外死亡袭蝗,警方通過查閱死者的電腦和手機(jī)唤殴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來到腥,“玉大人朵逝,你說我怎么就攤上這事∠绶叮” “怎么了配名?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晋辆。 經(jīng)常有香客問我段誊,道長,這世上最難降的妖魔是什么栈拖? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任连舍,我火速辦了婚禮,結(jié)果婚禮上涩哟,老公的妹妹穿的比我還像新娘索赏。我一直安慰自己,他們只是感情好贴彼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布潜腻。 她就那樣靜靜地躺著,像睡著了一般器仗。 火紅的嫁衣襯著肌膚如雪融涣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天精钮,我揣著相機(jī)與錄音威鹿,去河邊找鬼。 笑死轨香,一個(gè)胖子當(dāng)著我的面吹牛忽你,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臂容,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼科雳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脓杉?” 一聲冷哼從身側(cè)響起糟秘,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎球散,沒想到半個(gè)月后尿赚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年吼畏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了督赤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泻蚊,死狀恐怖躲舌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情性雄,我是刑警寧澤没卸,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站秒旋,受9級(jí)特大地震影響约计,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜迁筛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一煤蚌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧细卧,春花似錦尉桩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至止邮,卻和暖如春这橙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背导披。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工屈扎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盛卡。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓助隧,卻偏偏與公主長得像筑凫,于是被迫代替她去往敵國和親滑沧。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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

  • Defining the global fetch plan Retrieving persistent obje...
    ilaoke閱讀 5,532評(píng)論 1 6
  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 9,497評(píng)論 0 23
  • Querying with criteria and example 當(dāng)查詢需要?jiǎng)討B(tài)生成時(shí)巍实,使用Criteria可...
    ilaoke閱讀 2,880評(píng)論 0 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法滓技,類相關(guān)的語法,內(nèi)部類的語法棚潦,繼承相關(guān)的語法令漂,異常的語法,線程的語...
    子非魚_t_閱讀 31,631評(píng)論 18 399
  • 想一千次纬朝,不如做一次收叶。華麗的跌倒,勝過無謂的徘徊共苛! 1 上兩個(gè)月判没,一個(gè)心血來潮就加入了辭職大軍的隊(duì)伍,公司相處得比...
    jojokeys閱讀 393評(píng)論 0 0