HibernateはSQLに非常によく似た(意図的に似せた)強力な問い合わせ言語を備えています。 しかしSQLに似た構文に惑わされないでください。HQLは完全にオブジェクト指向であり、継承、ポリモーフィズム、関連といった概念を理解してください。
クエリはJavaのクラス名とプロパティ名を除いて大文字、小文字を区別しません。従ってSeLeCTはsELEct と同じで、かつSELECT とも同じですがnet.sf.hibernate.eg.FOOはnet.sf.hibernate.eg.Foo とは違い、かつfoo.barSet はfoo.BARSETとも違います。
このマニュアルでは小文字のHQLキーワードを使用します。大文字のキーワードのクエリの方が読みやすいと感じるユーザーもいると思います。しかし我々はJavaのコードにHQLを埋め込むときには、この習慣はコードを見づらくすると考えています。
可能な、もっとも単純なHibernateクエリは次の形式です。:
from eg.Cat
これは単純にeg.Catクラスのインスタンスをすべて返します。
ほとんどの場合クエリのほかの部分でCatを参照するので、エイリアスを割り当てる必要があるでしょう。
from eg.Cat as cat
このクエリはCatインスタンスにエイリアスのcatを割り当てます。そのため我々はあとのクエリでこのエイリアスを使用できます。asキーワードはオプションです。つまりこのように書けます。:
from eg.Cat cat
直積、つまりクロスジョインによって多数のクラスが出現することもあります。
from Formula, Parameter
from Formula as form, Parameter as param
ローカル変数のJavaのネーミング基準と一致した、頭文字に小文字を使ったクエリ・エイリアスをつけることはいい習慣です(例えばdomesticCat)。
関連するエンティティあるいは値コレクションの要素にさえ、ジョインを使ってエイリアスを割り当てることが出来ます。
from eg.Cat as cat inner join cat.mate as mate left outer join cat.kittens as kitten from eg.Cat as cat left join cat.mate.kittens as kittens from Formula form full join form.parameter param
サポートしているジョインのタイプはANSI SQLと同じです。
インナー・ジョイン
レフト・アウター・ジョイン
ライト・アウター・ジョイン
フル・ジョイン (たいていの場合使いづらい)
インナー・ジョイン、レフト・アウター・ジョイン、ライト・アウター・ジョインの構造は省略されることもあります。
from eg.Cat as cat join cat.mate as mate left join cat.kittens as kitten
加えて、ジョインによるフェッチは関連や値のコレクションを親オブジェクトと一緒に1度のselect句で初期化します。これは特にコレクションの場合に有用です。関連とコレクションに対するマッピング定義ファイルのアウター・ジョインとlazy初期化の宣言を効果的に無効にします。
from eg.Cat as cat inner join fetch cat.mate left join fetch cat.kittens
ジョインによるフェッチは関連するオブジェクトがwhere句(または他のどんな句でも)で使われてはならないので、通常エイリアスを割り当てる必要がありません。また関連オブジェクトは問い合わせ結果として直接返されません。代わりに親オブジェクトを通してアクセスできます。
現在の実装では、1つのクエリ中ではただひとつのコレクションのロールだけがフェッチされることに注意してください(その他のものはすべて事前にフォーマットされません)。またfetch構造はscroll()やiterate()を使ったクエリ呼び出しで使用できないことにも注意してください。 最後にアウター・ジョインによるフェッチとライト・ジョインによるフェッチは有用ではないことに注意してください。
select句は以下のようにどのオブジェクトと属性をクエリ・リザルトセットに返すかを選択します。:
select mate from eg.Cat as cat inner join cat.mate as mate
上のクエリは他のCatsのmatesを選択します。実際には次のように、より簡潔に表現できます。:
select cat.mate from eg.Cat cat
特別のelements関数を利用して、コレクション要素を選択できます。次のクエリは任意のCatのkittensをすべて返します。
select elements(cat.kittens) from eg.Cat cat
クエリはコンポーネント型のプロパティを含む、あらゆるバリュー・タイプのプロパティも返せます。:
select cat.name from eg.DomesticCat cat where cat.name like 'fri%' select cust.name.firstName from Customer as cust
クエリは多数のオブジェクトと(または)プロパティをObject[]型の配列として返せます。
select mother, offspr, mate.name from eg.DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr
あるいはFamilyクラスが適切なコンストラクタを持っているとするならば、
select new Family(mother, mate, offspr) from eg.DomesticCat as mother join mother.mate as mate left join mother.kittens as offspr
タイプ・セーフなJavaオブジェクトを返せます。
HQLのクエリはプロパティの集約関数も返せます。
select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat) from eg.Cat cat
コレクションはselect句の集約関数の内部にも記述できます。
select cat, count( elements(cat.kittens) ) from eg.Cat cat group by cat
サポートしている集約関数は以下のものです。
avg(...), sum(...), min(...), max(...)
count(*)
count(...), count(distinct ...), count(all...)
distinctとallキーワードはSQLと同じ意味で使われ、同じ意味を持っています。
select distinct cat.name from eg.Cat cat select count(distinct cat.name), count(cat) from eg.Cat cat
下のクエリはCatだけでなく、DomesticCatのようなサブクラスのインスタンスも返します。
from eg.Cat as cat
Hibernateクエリーはfrom句中で、どんなJavaクラスあるいはインターフェースも指定できます。クエリはそのクラスを拡張した、もしくはインターフェースを実装した全ての永続クラスを返します。次のクエリは永続オブジェクトをすべて返します:
from java.lang.Object o
Namedインターフェースは様々な永続クラスによって実装されます。:
from eg.Named n, eg.Named m where n.name = m.name
最後の2つのクエリは、2つ以上のSQL SELECTを要求していることに注意してください。 このことはorder by句がリザルトセット全体を正確には整列しないことを意味します(さらにそれは、Query.scroll()を使用してこれらのクエリを呼ぶことができないことを意味します。)。
where句は返されるインスタンスのリストを絞ることができます。
from eg.Cat as cat where cat.name='Fritz'
上のHQLはFritzという名前のCatを返します。
select foo from eg.Foo foo, eg.Bar bar where foo.startDate = bar.date
上のHQLは、FooのstartDateプロパティと等しいdateプロパティを持ったbarインスタンスが存在する、すべてのFooインスタンスを返します。コンパウンド・パス表現はwhere句を非常に強力にします。
from eg.Cat cat where cat.mate.name is not null
上のクエリはテーブル・ジョイン(インナー・ジョイン)を持つSQLクエリに変換します。
from eg.Foo foo where foo.bar.baz.customer.address.city is not null
もし上のクエリを記述したらクエリ内に4つのテーブル・ジョインを必要とするSQLクエリに変換されます。
=オペレーターは以下のように、プロパティだけでなくインスタンスを比較するためにも使われます。
from eg.Cat cat, eg.Cat rival where cat.mate = rival.mate select cat, mate from eg.Cat cat, eg.Cat mate where cat.mate = mate
id(小文字)は特別なプロパティであり、オブジェクトのユニークな識別子に参照を付けるために使用できます。(さらに、そのプロパティ名を使用できます。)
from eg.Cat as cat where cat.id = 123 from eg.Cat as cat where cat.mate.id = 69
2番目のクエリは効率的です。テーブル・ジョインを要求されません!
また複合識別子のプロパティも使用できます。ここでPersonがcountryとmedicareNumberからなる複合識別子を持つと仮定します。
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番目のクエリはテーブル・ジョインを要求されません。
同様にclassは特別なプロパティであり、ポリモーフィックな永続化におけるインスタンスの識別値にアクセスします。where句に埋め込まれたJavaのクラス名はその識別値に変換されます。
from eg.Cat cat where cat.class = eg.DomesticCat
またコンポーネントやコンポジット・ユーザ・タイプ(またそのコンポーネントのコンポーネントなど)のプロパティも指定できます。しかし決してコンポーネント・タイプのプロパティ中で終了するパス表現をしようとしないでください。例えばもしstore.ownerがaddressコンポーネントを持つエンティティならば以下のような結果となります。
store.owner.address.city // OK store.owner.address // エラー!
"any"型は特別なプロパティであるidとclassを持ち、以下の方法でジョインを表現することを可能にします(ただしAuditLog.itemは<any>でマッピングされたプロパティです)。
from eg.AuditLog log, eg.Payment payment where log.item.class = 'eg.Payment' and log.item.id = payment.id
log.item.classとpayment.classが上記のクエリ中で全く異なるデータベース・カラムの値を参照するということに注意してください。
SQLのwhere句で記述することが出来る式のほとんどをHQLでも記述できます。:
数学的なオペレーター:+, -, *, /
2項比較演算:=, >=, <=, <>, !=, like
論理演算:and, or, not
ストリングの連結:||
SQLのスカラー関数:upper() , lower()など
グループ分けを表す括弧:( )
in, between, is null
JDBC入力パラメータ:?
名前付きパラメータ::name, :start_date, :x1
SQL文字列:'foo', 69, '1970-01-01 10:00:01.0'
Javaのpublic static final定数:eg.Color.TABBY
in と betweenは以下のように使用できます。:
from eg.DomesticCat cat where cat.name between 'A' and 'B' from eg.DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )
また、否定の形式は下のように記述されます。
from eg.DomesticCat cat where cat.name not between 'A' and 'B' from eg.DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )
同様にis null や is not nullはnull値をテストするために使用できます。
booleanはHibernateの設定ファイルでHQL query substitutionsを宣言することで、式として簡単に使用できます。:
<property name="hibernate.query.substitutions">true 1, false 0</property>
こうすることで下記のHQLをSQLに変換するときにtrue , falseキーワードを1 , 0に置き換えます。:
from eg.Cat cat where cat.alive = true
特別なプロパティsize、または特別な関数size()を使ってコレクションのサイズをテストできます。:
from eg.Cat cat where cat.kittens.size > 0 from eg.Cat cat where size(cat.kittens) > 0
インデックスが付けられたコレクションについては、minIndex と maxIndexを使用して最小および最大のインデックスが参照できます。同様にminElement と maxElementを使用して、基本型のコレクション要素の最小と最大が参照できます。
from Calendar cal where cal.holidays.maxElement > current date
さらに関数形式もあります。(それらは上記の構造と異なり、大文字小文字を区別しません。):
from Order order where maxindex(order.items) > 100 from Order order where minelement(order.items) > 10000
コレクションの要素やインデックスの集合(elements , indices関数)、またはサブクエリの結果が渡される場合には、SQL関数any, some, all, exists, inがサポートされます。
select mother from eg.Cat as mother, eg.Cat as kit where kit in elements(foo.kittens) select p from eg.NameList list, eg.Person p where p.name = some elements(list.names) from eg.Cat cat where exists elements(cat.kittens) from eg.Player p where 3 > all elements(p.scores) from eg.Show show where 'fizard' in indices(show.acts)
これらsize, elements, indices, minIndex, maxIndex, minElement, maxElementの構造には、以下の使用上の制限があることに注意してください。:
where句では:副問い合わせを備えたデータベースでのみ使用可能。
select句では:要素および索引だけが使用可能。
インデックスが付けられたコレクション(配列、list、map)の要素はindexによって引用できます(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はさらにmany-to-one関連の要素、あるいは値のコレクションに対してindex()関数を用意しています。
select item, index(item) from Order order join order.items item where index(item) < 5
ベースとなるデータベースがサポートしているスカラーSQL関数が使用できます
from eg.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)
Hint:例えばこのように出来ます。
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 eg.DomesticCat cat order by cat.name asc, cat.weight desc, cat.birthdate
オプションのasc と descはそれぞれ昇順か降順の整列を示します
集約値を返すクエリは、返されたクラスやコンポーネントの任意のプロパティによってグループ化できます。:
select cat.color, sum(cat.weight), count(cat) from eg.Cat cat group by cat.color select foo.id, avg( elements(foo.names) ), max( indices(foo.names) ) from eg.Foo foo group by foo.id
注:副問い合わせのないデータベースでも、select節内部を構成するelements および indicesを使用できます。
having句も同様に可能です。
select cat.color, sum(cat.weight), count(cat) from eg.Cat cat group by cat.color having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
もしベースとなるデータベースによってサポートされているなら、having 句と order by句中でSQL関数および集計関数の使用は可能です(つまりMySQLでは使用不可能です)。
select cat from eg.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句も演算式を含むことが出来ないことに注意してください。
副問い合わせをサポートするデータベースについてはHibernateはクエリ内でサブクエリをサポートします。括弧(SQLの集約関数呼び出しによる事が多い)でサブクエリを囲まなければなりません。関連副問い合わせ(外部クエリ中の別名を参照するサブサブクエリのこと)さえ許可されます。
from eg.Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from eg.DomesticCat cat ) from eg.DomesticCat as cat where cat.name = some ( select name.nickName from eg.Name as name ) from eg.Cat as cat where not exists ( from eg.Cat as mate where mate.mate = cat ) from eg.DomesticCat as cat where cat.name not in ( select name.nickName from eg.Name as name )
Hibernateクエリは非常に強力で複雑にできます。実際、クエリ言語の威力はHibernateの主要なセールス・ポイントの一つです。ここに最近のプロジェクトで使用したクエリと非常によく似た例があります。ほとんどのクエリはこれらの例より簡単に記述できることに注意してください!
以下のクエリは特定の顧客と与えられた最小の合計値に対する未払い注文の注文ID、商品の数、注文の合計を合計値で整列して返します。結果として返されるSQLクエリはORDER, ORDER_LINE, PRODUCT, CATALOG および PRICEテーブルに対し4つのinner joinと(関連しない)副問い合わせを持ちます。
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サーバーのisNull()関数を使用しています。このクエリは3つのインナー・ジョインと1つのアウター・ジョイン 、そしてACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION および 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
実際に結果を返さなくてもクエリの結果数を数えることができます。:
( (Integer) session.iterate("select count(*) from ....").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.name 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 in class Foo where foo.name=:name and foo.size=:size"); q.setProperties(fooBean); // fooBeanはgetName()と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.iterate("select count(*) from ....").next() ).intValue();