Hibernate.orgCommunity Documentation

第15章 HQL: Hibernate クエリ言語

15.1. 大文字と小文字の区別
15.2. from 節
15.3. 関連と結合
15.4. 結合構文の形式
15.5. 識別子プロパティの参照
15.6. Select 節
15.7. 集約関数
15.8. ポリモーフィズムを使ったクエリ
15.9. where 節
15.10. Expressions 式
15.11. order by 節
15.12. group by 節
15.13. 副問い合わせ
15.14. HQL の例
15.15. 大量の UPDATE と DELETE
15.16. Tips & Tricks
15.17. コンポーネント
15.18. 行値コンストラクタ構文

Hibernate は SQL に非常によく似た (意図的に似せた) 強力な問い合わせ言語を備えています。しかし SQL に似た構文に惑わされないでください。 HQL は完全にオブジェクト指向であり、継承、ポリモーフィズム、関連といった概念を理解します。

クエリは Java のクラス名とプロパティ名を除いて大文字、小文字を区別しません。従って SeLeCTsELEct と同じで、かつ SELECT とも同じですが org.hibernate.eg.FOOorg.hibernate.eg.Foo とは違い、かつ foo.barSetfoo.BARSET とも違います。

このマニュアルでは小文字の HQL キーワードを使用します。大文字のキーワードのクエリの方が読みやすいと感じるユーザーもいると思います。ですが、 Java コード内に埋め込まれたときには見づらいと思います。

もっとも単純な Hibernate クエリは次の形式です:

from eg.Cat

This returns all instances of the class eg.Cat. You do not usually need to qualify the class name, since auto-import is the default. For example:

from Cat

In order to refer to the Cat in other parts of the query, you will need to assign an alias. For example:

from Cat as cat

このクエリでは Cat インスタンスに cat という別名を付けています。そのため、後でこのクエリ内で、この別名を使うことができます。 as キーワードはオプションです。つまりこのように書くこともできます:

from Cat cat

直積、あるいは「クロス」結合によって多数のクラスが出現することもあります。

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

ローカル変数の Java のネーミング基準と一致した、頭文字に小文字を使ったクエリの別名を付けることはいい習慣です (例えば domesticCat)。

関連するエンティティあるいは値コレクションの要素にも、 結合 を使って別名を割り当てることが出来ます。

from Cat as cat
    inner join cat.mate as mate
    left outer join cat.kittens as kitten
from Cat as cat left join cat.mate.kittens as kittens
from Formula form full join form.parameter param

サポートしている結合のタイプは ANSI SQL と同じです。

inner joinleft outer joinright outer join には省略形を使うこともできます。

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

HQL の with キーワードを使うと、結合条件を付け加えることができます。

from Cat as cat
    left join cat.kittens as kitten
        with kitten.bodyWeight 
> 10.0

A "fetch" join allows associations or collections of values to be initialized along with their parent objects using a single select. This is particularly useful in the case of a collection. It effectively overrides the outer join and lazy declarations of the mapping file for associations and collections. See 「フェッチ戦略」 for more information.

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

結合によるフェッチは関連するオブジェクトが where 節 (または他のどんな節でも) で使われてはならないので、通常別名を割り当てる必要がありません。また関連オブジェクトは問い合わせ結果として直接返されません。代わりに親オブジェクトを通してアクセスできます。コレクションを再帰的に結合フェッチする場合のみ、別名が必要になります:

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

fetch 構文は iterate() を使ったクエリ呼び出しで使用できないことに注意してください (一方で scroll() は使用できます)。また、これらの操作は結果の行に基づいているため、 fetchsetMaxResults()setFirstResult() と一緒に使用すべきではありません。通常 eager なコレクションフェッチをすると重複が出てしまうため、あなたが期待するような行数にはならないのです。そしてまた fetch は、アドホックな with 条件を一緒に使うこともできません。一つのクエリで複数のコレクションを結合フェッチすることにより直積を作成できるので、この場合注意してください。また、複数のコレクションに対する結合フェッチは bag マッピングに対して予期せぬ結果をもたらすことがあるので、この場合のクエリの作成には注意してください。最後に 全外部結合によるフェッチ右外部結合によるフェッチ は有用ではないことに注意してください。

もしプロパティレベルの遅延フェッチを使う場合(内部的にバイトコード処理をする場合)、 fetch all properties を使うことで Hibernate に遅延プロパティを速やかに(最初のクエリで)フェッチさせることができます。

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

HQL は2つの関連結合形式をサポートします: 暗黙的明示的

これまでのセクションでお見せした使い方はすべて 明示的な 形式で、 from 節で明示的に join キーワードを使っています。この形式をおすすめします。

暗黙的 フォームは、 join キーワードを使いません。代わりに、参照する関連にドット表記を使います。 暗黙的 結合は、さまざまな HQL に出てきます。 暗黙的 結合の結果は、 SQL ステートメントの内部結合結果です。

from Cat as cat where cat.mate.name like '%s%'

エンティティの識別子プロパティは、一般的に2つの方法で参照されます:

複合識別子プロパティへの参照は同じ命名ルールに従います。もしエンティティが id と名付けられた非識別子プロパティを持っていたら、複合識別子プロパティはその定義された名前で参照することができます。そうでないと、特別な id プロパティは、識別子プロパティを参照するのに使用されます。

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 として、

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

あるいは Family クラスが適切なコンストラクタを持っているとするならば、

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

select 節に as を使って別名をつけることもできます。

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

select new map と一緒に使うときに最も役立ちます:

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

このクエリは別名から select した値へ Map を返します。

HQL のクエリはプロパティの集約関数の結果も返せます:

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

サポートしている集約関数は以下のものです。

select 節において算術操作、連結と承認された SQL 関数を使うことができます:

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

SQL と同じ意味を持つ distinctall キーワードを使うことができます。

select distinct cat.name from Cat cat

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

次のようなクエリ:

from Cat as cat

Cat インスタンスだけではなく、 DomesticCat のようなサブクラスも返されます。 Hibernate クエリは どんな Java クラスやインターフェースも from 節に入れることができます。クエリはそのクラスを拡張した、もしくはインターフェースを実装した全ての永続クラスを返します。次のクエリは永続オブジェクトをすべて返します:

from java.lang.Object o

Named インターフェースは様々な永続クラスによって実装されます。:

from Named n, Named m where n.name = m.name

最後の2つのクエリは、2つ以上の SQL SELECT を要求していることに注意してください。このことは order by 節がリザルトセット全体を正確には整列しないことを意味します (さらにそれは、 Query.scroll() を使用してこれらのクエリを呼ぶことができないことを意味します。)。

where 節は返されるインスタンスのリストを絞ることができます。もし別名がない場合、名前でプロパティを参照します。

from Cat where name='Fritz'

もし別名がある場合、修飾名を使ってください:

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

名前が 'Fritz' という Cat のインスタンスを返します。

The following query:

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

上の HQL は、 FoostartDate プロパティと等しい date プロパティを持った bar インスタンスが存在する、すべての Foo インスタンスを返します。コンパウンドパス式は where 節を非常に強力にします。注目:

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

このクエリはテーブル結合(内部結合)を持つ SQL クエリに変換されます。その代わりに以下のように書くと、

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

もし上のクエリを記述したらクエリ内に4つのテーブル結合を必要とする SQL クエリに変換されます。

= 演算子は以下のように、プロパティだけでなくインスタンスを比較するためにも使われます。:

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

The special property (lowercase) id can be used to reference the unique identifier of an object. See 「識別子プロパティの参照」 for more information.

from Cat as cat where cat.id = 123

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

2番目のクエリは効率的です。テーブル結合が必要ありません。

また複合識別子のプロパティも使用できます。ここで PersoncountrymedicareNumber からなる複合識別子を持つと仮定します。識別子プロパティ参照についての詳細は、前回と同様に 「識別子プロパティの参照」 を参照ください。

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

繰り返しますが、2番目のクエリにはテーブル結合が必要ありません。

See 「識別子プロパティの参照」 for more information regarding referencing identifier properties)

同様に class は特別なプロパティであり、ポリモーフィックな永続化におけるインスタンスの discriminator 値にアクセスします。 where 節に埋め込まれた Java のクラス名はその discriminator 値に変換されます。

from Cat cat where cat.class = DomesticCat

You can also use components or composite user types, or properties of said component types. See 「コンポーネント」 for more information.

"any" 型は特別なプロパティである idclass を持ち、以下の方法で結合を表現することを可能にします (AuditLog.item<any> でマッピングされたプロパティです)。

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

log.item.classpayment.class が上記のクエリ中で全く異なるデータベースカラムの値を参照するということに注意してください。

Expressions used in the where clause include the following:

inbetween は以下のように使用できます:

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

また、否定形で記述することもできます。

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

同様に is nullis not null は null 値をテストするために使用できます。

Hibernate 設定ファイルで HQL query substitutions を定義すれば、 boolean 値を式の中で簡単に使用できます:

<property name="hibernate.query.substitutions"
>true 1, false 0</property
>

こうすることで下記の HQL を SQL に変換するときに truefalse キーワードは 10 に置き換えられます:

from Cat cat where cat.alive = true

特別なプロパティ size、または特別な関数 size() を使ってコレクションのサイズをテストできます:

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

インデックス付きのコレクションでは、 minindexmaxindex 関数を使って、インデックスの最小値と最大値を参照できます。同様に、 minelementmaxelement を使って、基本型のコレクション要素の最小値と最大値を参照できます。

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

コレクションの要素やインデックスのセット(elementsindices 関数)、または副問い合わせ(後述)の結果が受け取れるときは、 SQL 関数 any, some, all, exists, in がサポートされます。

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)
from Cat cat where exists elements(cat.kittens)
from Player p where 3 
> all elements(p.scores)
from Show show where 'fizard' in indices(show.acts)

sizeelementsindicesminindexmaxindexminelementmaxelement は Hibernate3 の where 節だけで利用可能であることに注意してください。

インデックス付きのコレクション(arrays, lists, maps)の要素は、インデックスで参照できます(where節内でのみ):

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 関数が使用できます:

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
    )

クエリが返す list は、返されるクラスやコンポーネントの任意の属性によって並べ替えられます:

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)

もし使用するデータベースがサポートしているなら、 havingorder by 節で SQL 関数と集約関数が使えます(例えば MySQL にはありません)。

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

group by 節や order by 節に算術式を含むことができないことに注意してください。また、 Hibernate は今のところグループエンティティを拡張しないことにも注意してください。したがって、もし cat の全てのプロパティが非集合体の場合、 group by cat を書くことはできません。全ての非集合体のプロパティを明示的にリストする必要があります。

サブセレクトをサポートするデータベースのため、 Hibernate は副問い合わせをサポートしています。副問い合わせは括弧で囲まなければなりません( SQL の集約関数呼び出しによる事が多いです)。関連副問い合わせ (外部クエリ中の別名を参照する副問い合わせのこと) さえ許可されます。

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

HQL 副問い合わせは、 select または where 節だけで使われることに注意してください。

Note that subqueries can also utilize row value constructor syntax. See 「行値コンストラクタ構文」 for more information.

Hibernate クエリは非常に強力で複雑にできます。実際、クエリ言語の威力は Hibernate の主要なセールスポイントの一つです。ここに最近のプロジェクトで使用したクエリと非常によく似た例があります。ほとんどのクエリはこれらの例より簡単に記述できることに注意してください。

以下のクエリは特定の顧客と与えられた最小の合計値に対する未払い注文の注文 ID 、商品の数、注文の合計を合計値で整列して返します。価格を決定する際、現在のカタログを使います。結果として返される SQL クエリは ORDERORDER_LINEPRODUCTCATALOG および PRICE テーブルに対し4つの内部結合と (関連しない) 副問い合わせを持ちます。

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 である場合を除きます。このクエリは2つの内部結合と PAYMENT, PAYMENT_STATUS および PAYMENT_STATUS_CHANGE テーブルに対する関連副問い合わせを備えた SQL クエリに変換されます。

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

もし set の代わりに list として 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

次のクエリは現在のユーザーが所属する組織に対するアカウントおよび未払いの支払いをすべて返す MS SQL Server の isNull() 関数を使用しています。このクエリは3つの内部結合と1つの外部結合、そして ACCOUNTPAYMENTPAYMENT_STATUSACCOUNT_TYPEORGANIZATION および ORG_USER テーブルに対する副問い合わせ持った SQL に変換されます。

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

いくつかのデータベースについては、 (関連させられた) 副問い合わせの使用を避ける必要があるでしょう。

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

HQL now supports update, delete and insert ... select ... statements. See 「DML スタイルの操作」 for more information.

実際に結果を返さなくてもクエリの結果数を数えることができます:

( (Integer) session.createQuery("select count(*) from ....").iterate().next() ).intValue()

コレクションのサイズにより結果を並べ替えるためには以下のクエリを使用します:

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
    join usr.messages msg
group by usr.id, usr.name
having count(msg) 
>= 1

内部結合をしているせいで上の解決法が message の件数がゼロの 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

JavaBean のプロパティは、名前付きのクエリパラメータに結びつけることが出来ます:

Query q = s.createQuery("from foo Foo as foo where foo.name=:name and foo.size=:size");

q.setProperties(fooBean); // fooBean has getName() and getSize()
List foos = q.list();

コレクションはフィルタ付き Query インターフェースを使用することでページをつけることができます:

Query q = s.createFilter( collection, "" ); // the trivial filter

q.setMaxResults(PAGE_SIZE);
q.setFirstResult(PAGE_SIZE * pageNumber);
List page = q.list();

コレクションの要素はクエリフィルタを使って、並べ替えやグループ分けが出来ます:

Collection orderedCollection = s.filter( collection, "order by this.amount" );

Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );

コレクションを初期化せずにコレクションのサイズを得ることができます:

( (Integer) session.createQuery("select count(*) from ....").iterate().next() ).intValue();

HQL クエリでシンプルな値型を使用できるので、コンポーネントは、あらゆる点で使用できます。これは select 節の中に現われます:

select p.name from Person p
select p.name.first from Person p

人名のプロパティがコンポーネントの場所。コンポーネントは、 where 節でも使用可能です:

from Person p where p.name = :name
from Person p where p.name.first = :firstName

コンポーネントは order by 節でも使用可能です:

from Person p order by p.name
from Person p order by p.name.first

Another common use of components is in row value constructors.

下に位置するデータベースが ANSI SQL row value constructor 構文 (tuple 構文とよばれることもあります) をサポートしていないとしても、 HQL はその使用をサポートしています。ここでは、一般的にコンポーネントと連繋するマルチバリュー比較について触れます。ネームコンポーネントを定義する Person エンティティを考えましょう:

from Person p where p.name.first='John' and p.name.last='Jingleheimer-Schmidt'

それは少々詳細になりますが、有効な構文です。より簡潔にし、 row value constructor 構文を使用するのがよいでしょう:

from Person p where p.name=('John', 'Jingleheimer-Schmidt')

それを select 節で指定するのも効果的です。

select p.name from Person p

次に row value constructor 構文の使用が有効なときは、サブクエリを使用して複数の値と比較する必要があるときです:

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

この構文を使用するかどうか決定するときに考慮しなければならないことは、クエリがメタデータ内のコンポーネントのサブプロパティの順番に依存していることです。