Hibernate.orgCommunity Documentation
データベースのネイティブ SQL 方言を使ってクエリを表現することもできます。クエリヒントや Oracle の CONNECT
キーワードのように、データベース独自の機能を利用したいときに使えます。 SQL/JDBC を直接使用しているアプリケーションから Hibernate への移行も容易にしています。
Hibernate3 では、生成、更新、削除、読み込み処理のようなすべての SQL (ストアドプロシージャを含む)を手書きできます。
ネイティブな SQL クエリの実行は SQLQuery
インターフェースを通して制御します。 SQLQuery
インターフェースは Session.createSQLQuery()
を呼び出して取得します。この API を使って問い合わせする方法を以下で説明します。
最も基本的な SQL クエリはスカラー(値)のリストを得ることです。
sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
これらはどちらも、 CATS テーブルの各カラムのスカラー値を含む Object 配列(Object[])のリストを返します。返すスカラー値の実際の順番と型を推定するために、 Hibernate は ResultSetMetadata を使用します。
ResultSetMetadata
を使用するオーバーヘッドを避けるため、もしくは単に何が返されるか明確にするため、 addScalar()
を使えます。
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
このクエリで指定されているものを下記に示します:
SQL クエリ文字列
返されるカラムと型
これはまだ Object 配列を返しますが、 ResultSetMetdata
を使用しません。ただし、その代わりに基礎にあるリザルトセットから ID、NAME、BIRTHDATE カラムをそれぞれ Long、String、Short として明示的に取得します。これは3つのカラムを返すのみであることも意味します。たとえ、クエリが *
を使用し、列挙した3つより多くのカラムを返せるとしてもです。
スカラーの型情報を省くこともできます。
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME")
.addScalar("BIRTHDATE")
これは本質的に前と同じクエリですが、 NAME と BIRTHDATE の型を決めるために ResultSetMetaData
を使用します。一方、 ID の型は明示的に指定されています。
ResultSetMetaData から返される java.sql.Types を Hibernate の型に マッピングすることは、 Dialect が制御します。明示された型がマッピングされていないか、結果の型が期待したものと異なる場合、 Dialect の registerHibernateType
を呼び出し、カスタマイズできます。
ここまでのクエリは、すべてスカラー値を返すものでした。基本的に、リザルトセットから「未加工」の値を返します。以降では、 addEntity()
により、ネイティブ SQL クエリからエンティティオブジェクトを取得する方法を示します。
sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
このクエリで指定されているものを下記に示します:
SQL クエリ文字列
クエリが返すエンティティと SQL テーブルの別名
Cat が ID 、 NAME 、 BIRTHDATE のカラムを使ってクラスにマッピングされる場合、上記のクエリはどちらも、要素が Cat エンティティであるリストを返します。
エンティティを別のエンティティに 多対一
でマッピングしている場合は、ネイティブクエリを実行する際に、この別のエンティティを返すことも要求します。さもなければ、データベース固有の「column not found(カラムが見つかりません)」エラーが発生します。 * 表記を使用した際は、追加のカラムが自動的に返されますが、次の例のように、 Dog
に 多対一
であることを明示することを私たちは好みます。
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
これにより cat.getDog() が正しく機能します。
プロキシを初期化するための余分な処理を避けるため、 Dog
の中で即時結合できます。これは addJoin()
メソッドにより行います。関連もしくはコレクションに結合できます。
sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dog");
この例の中で、返される Cat
は、データベースへの余分処理なしで、完全に初期化された dog
プロパティを持ちます。結合対象のプロパティへのパスを指定できるように、別名(「cat」)を追加したことに注意してください。コレクションの即時結合も同じようにできます。たとえば、 Cat
が一対多で Dog
を持っていた場合、次のようになります。
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dogs");
現在のところ、 Hibernate で使いやすくするための SQL クエリの拡張なしに、ネイティブクエリで何かを可能にする限界に来ています。同じ型のエンティティを複数返す際や、デフォルトの別名や列名で十分ではない場合に、問題は起こり始めます。
ここまでは、リザルトセットのカラム名は、マッピングドキュメントで指定されたカラム名と同じであると仮定していました。複数のテーブルが同じカラム名を持つ場合があるため、複数テーブルを結合する SQL クエリで問題となる場合があります。
下記のような(失敗しそうな)クエリでは、カラム別名インジェクション(column alias injection)が必要です:
sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
このクエリの意図は、1行ごとに2つの Cat インスタンス、つまり猫とその母親を返すということです。同じカラム名にマッピングすることにより名前が衝突するため、このクエリは失敗します。ベータベースによっては、返されるカラムの別名が "c.ID"、"c.NAME" などの形式であり、マッピングで指定されたカラム("ID" と "NAME")と等しくないため、失敗します。
下記の形式は、カラム名が重複しても大丈夫です:
sess.createSQLQuery("SELECT {cat.*}, {mother.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
このクエリで指定されているものを下記に示します:
SQL クエリ文字列 (Hibernate がカラムの別名を挿入するためのプレースホルダを含む)
クエリによって返されるエンティティ
上記で使用している {cat.*} と {mother.*} という表記は、「すべてのプロパティ」を表す省略形です。代わりに、明示的にカラムを列挙してもよいですが、その場合は、 Hibernate に各プロパティに対応する SQL カラムの別名を挿入させるべきでしょう。カラムの別名のためのプレースホルダは、テーブルの別名によって修飾されたプロパティ名です。下記の例では、別のテーブル cat_log から マッピングメタデータで定義された Cat とその母親を復元します。もし好むなら、 where 節の中でも、プロパティの別名を使えます。
String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +
"BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
"FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
List loggedCats = sess.createSQLQuery(sql)
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class).list()
多くの場合、上記のような別名インジェクションが必要です。ただし、複合プロパティ、継承識別子、コレクションなどのようなより複雑なマッピングと関連するクエリがなければです。ある特定の別名を使用することにより、 Hibernate は適切な別名を挿入できます。
別名インジェクションとして使用できるものを下表に示します。注記:下表の別名は一例です。それぞれの別名は一意であり、使用する際にはおそらく異なる名前を持ちます。
表17.1 別名に挿入する名前
説明 | 構文 | 例 |
---|---|---|
単純なプロパティ | {[aliasname].[propertyname] | A_NAME as {item.name} |
複合プロパティ | {[aliasname].[componentname].[propertyname]} | CURRENCY as {item.amount.currency}, VALUE as {item.amount.value} |
エンティティのクラスを識別する値 | {[aliasname].class} | DISC as {item.class} |
エンティティの全プロパティ | {[aliasname].*} | {item.*} |
コレクションのキー | {[aliasname].key} | ORGID as {coll.key} |
コレクションの ID | {[aliasname].id} | EMPID as {coll.id} |
コレクションの要素 | {[aliasname].element} | XID as {coll.element} |
コレクションの要素のプロパティ | {[aliasname].element.[propertyname]} | NAME as {coll.element.name} |
コレクションの要素の全プロパティ | {[aliasname].element.*} | {coll.element.*} |
All properties of the collection | {[aliasname].*} | {coll.*} |
ネイティブ SQL クエリに ResultTransformer を適用できます。下記のように、例えば、管理されていないエンティティを返します。
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
このクエリで指定されているものを下記に示します:
SQL クエリ文字列
結果を変換したもの
上記のクエリは、インスタンス化し、 NAME と BIRTHDATE の値を対応するプロパティもしくはフィールドに挿入した CatDTO
のリストを返します。
ネイティブ SQL クエリは、以下のように、名前付きパラメータ(:name)と同様に位置パラメータをサポートします:
Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);
List pusList = query.setString(0, "Pus%").list();
query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class);
List pusList = query.setString("name", "Pus%").list();
名前付き SQL クエリはマッピングドキュメントで定義することができ、名前付き HQL クエリと全く同じ方法で呼ぶことができます。この場合、 addEntity()
を呼び出す必要は ありません 。
<sql-query name="persons">
<return alias="person" class="eg.Person"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex}
FROM PERSON person
WHERE person.NAME LIKE :namePattern
</sql-query
>
List people = sess.getNamedQuery("persons")
.setString("namePattern", namePattern)
.setMaxResults(50)
.list();
The <return-join>
element is use to join associations and the <load-collection>
element is used to define queries which initialize collections,
<sql-query name="personsWith">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
WHERE person.NAME LIKE :namePattern
</sql-query
>
名前付き SQL クエリはスカラ値を返すこともできます。 <return-scalar>
要素を使って、列の別名と Hibernate の型を宣言しなければなりません:
<sql-query name="mySqlQuery">
<return-scalar column="name" type="string"/>
<return-scalar column="age" type="long"/>
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query
>
リザルトセットのマッピング情報を <resultset>
に外部化することができます。複数の名前付きクエリで再利用したり、 setResultSetMapping()
API を通して再利用したりできます。
<resultset name="personAddress">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
</resultset>
<sql-query name="personsWith" resultset-ref="personAddress">
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
WHERE person.NAME LIKE :namePattern
</sql-query
>
代わりに、 hbm ファイル内のリザルトセットのマッピング情報を直接 Java コードの中で使用できます。
List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.setResultSetMapping("catAndKitten")
.list();
別名を挿入するために {}
構文を使う代わりに、 <return-property>
を使い、どの列の別名を使うのかを明示できます。
<sql-query name="mySqlQuery">
<return alias="person" class="eg.Person">
<return-property name="name" column="myName"/>
<return-property name="age" column="myAge"/>
<return-property name="sex" column="mySex"/>
</return>
SELECT person.NAME AS myName,
person.AGE AS myAge,
person.SEX AS mySex,
FROM PERSON person WHERE person.NAME LIKE :name
</sql-query>
<return-property>
は複数の列も扱えます。これは、複数列のプロパティをきめ細かく制御できないという、 {}
構文の制限を解決します。
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="endDate" column="myEndDate"/>
</return>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
FROM EMPLOYMENT
WHERE EMPLOYER = :id AND ENDDATE IS NULL
ORDER BY STARTDATE ASC
</sql-query
>
この例では、挿入のための {}
構文といっしょに、 <return-property>
を使っていることに注意してください。列とプロパティをどのように参照するかを選べます。
マッピングに discriminator が含まれている場合、 discriminator の列を指定するために、 <return-discriminator>
を使わなければなりません。
Hibernate はバージョン3から、ストアドプロシージャとストアド関数経由の問い合わせがサポートされました。以降の文書の多くは、両方に当てはまります。ストアドプロシージャやストアド関数を Hibernate で使うためには、1番目の出力パラメータとしてリザルトセットを返さなければなりません。 Oracle 9(もしくはそれ以上のバージョン)のストアドプロシージャの例を以下に示します:
CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR
AS
st_cursor SYS_REFCURSOR;
BEGIN
OPEN st_cursor FOR
SELECT EMPLOYEE, EMPLOYER,
STARTDATE, ENDDATE,
REGIONCODE, EID, VALUE, CURRENCY
FROM EMPLOYMENT;
RETURN st_cursor;
END;
Hibernate でこのクエリを使うためには、名前付きクエリでマッピングする必要があります。
<sql-query name="selectAllEmployees_SP" callable="true">
<return alias="emp" class="Employment">
<return-property name="employee" column="EMPLOYEE"/>
<return-property name="employer" column="EMPLOYER"/>
<return-property name="startDate" column="STARTDATE"/>
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EID"/>
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query
>
注記:今のところ、ストアドプロシージャはスカラとエンティティを返すのみです。 <return-join>
と <load-collection>
はサポートされていません。
Hibernate でストアドプロシージャや関数を使うためには、そのプロシージャはいくつかのルールに準拠する必要があります。ルールに準拠していないプロシージャは、 Hibernate で使うことはできません。それでも、準拠していないプロシージャを使いたいのであれば、 session.connection()
を通じて実行しなければなりません。ルールはデータベースごとに異なります。ストアドプロシージャのセマンティックスとシンタックスは、データベースベンダごとに異なるためです。
setFirstResult()/setMaxResults()
を使って、ストアドプロシージャクエリをページ分けすることはできません。
推奨する呼び出し方は、標準である SQL92 に従うことです。 { ? = call functionName(<parameters>) }
や { ? = call procedureName(<parameters>}
です。ネイティブな呼び出し構文はサポートされていません。
Oracle には下記のルールが適用されます:
関数はリザルトセットを返さなければなりません。プロシージャの第一引数はリザルトセットを返すため、 OUT
でなければなりません。 Oracle 9 と 10 では、 SYS_REFCURSOR
を使うことによってできます。 Oracle では REF CURSOR
型を定義する必要があります。 Oracle の文献を参照してください。
Sybase と MS SQL サーバーに適用されるルールを下記に示します:
プロシージャはリザルトセットを返さなければなりません。サーバーは複数のリザルトセットと更新カウントを返しますが、 Hibernate は1つ目のリザルトセットだけを返すことに注意してください。その他はすべて捨てられます。
プロシージャの中で SET NOCOUNT ON
を有効にできれば、おそらく効率がよくなるでしょう。しかし、これは必要条件ではありません。
Hibernate3 can use custom SQL for create, update, and delete operations. The SQL can be overridden at the statement level or inidividual column level. This section describes statement overrides. For columns, see 「Column read and write expressions」.
The class and collection persisters in Hibernate already contain a set of configuration time generated strings (insertsql, deletesql, updatesql etc.). The mapping tags <sql-insert>
, <sql-delete>
, and <sql-update>
override these strings:
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<sql-insert
>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update
>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete
>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class
>
SQL を直接データベースで実行するため、好みの方言を自由に使用できます。データベース独自の SQL を使えば、当然マッピングのポータビリティが下がります。
callable
属性をセットすれば、ストアドプロシージャを使用できます:
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<sql-insert callable="true"
>{call createPerson (?, ?)}</sql-insert>
<sql-delete callable="true"
>{? = call deletePerson (?)}</sql-delete>
<sql-update callable="true"
>{? = call updatePerson (?, ?)}</sql-update>
</class
>
今のところ、位置パラメータの順番はとても重要です。すなわち、 Hibernate が期待する順序でなければなりません。
org.hiberante.persister.entity
レベルのデバッグログを有効にすることによって、期待される順番を確かめられます。このレベルを有効にすることにより、エンティティの作成、更新、削除などで使用される静的な SQL が出力されます。(期待される順序を確認するためには、 Hibernate が生成する静的な SQL をオーバーライドするカスタム SQL をマッピングファイルに含めないことを忘れないでください。)
ストアドプロシージャは挿入/更新/削除された行数を返す必要があります(読み込みの場合は、返さないよりは返す方がよいです)。実行時に Hibernate が SQL 文の成功をチェックするからです。 Hibernate は、 CUD 処理のための数値の出力パラメータとして、 SQL 文の最初のパラメータをいつも記録します:
CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2) RETURN NUMBER IS BEGIN update PERSON set NAME = uname, where ID = uid; return SQL%ROWCOUNT; END updatePerson;
You can also declare your own SQL (or HQL) queries for entity loading. As with inserts, updates, and deletes, this can be done at the individual column level as described in 「Column read and write expressions」 or at the statement level. Here is an example of a statement level override:
<sql-query name="person">
<return alias="pers" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query
>
これは、まさに(以前議論した)名前付きクエリの宣言です。この名前付きクエリをクラスのマッピングから参照できます:
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<loader query-ref="person"/>
</class
>
これはストアドプロシージャでさえも動作します。
次のように、コレクションをロードするためのクエリさえ定義してよいです:
<set name="employments" inverse="true">
<key/>
<one-to-many class="Employment"/>
<loader query-ref="employments"/>
</set
>
<sql-query name="employments">
<load-collection alias="emp" role="Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query
>
次のように、結合フェッチによりコレクションをロードするエンティティローダーを定義できます:
<sql-query name="person">
<return alias="pers" class="Person"/>
<return-join alias="emp" property="pers.employments"/>
SELECT NAME AS {pers.*}, {emp.*}
FROM PERSON pers
LEFT OUTER JOIN EMPLOYMENT emp
ON pers.ID = emp.PERSON_ID
WHERE ID=?
</sql-query
>
製作著作 © 2004 Red Hat, Inc.