Hibernate.orgCommunity Documentation

第17章 ネイティブ SQL

17.1. Using a SQLQuery
17.1.1. スカラーのクエリ
17.1.2. エンティティのクエリ
17.1.3. 関連とコレクションの操作
17.1.4. 複数エンティティの取得
17.1.5. 管理されていないエンティティの取得
17.1.6. 継承の制御
17.1.7. パラメータ
17.2. 名前付き SQL クエリ
17.2.1. 列と列の別名を明示的に指定するために return-property を使う
17.2.2. 問い合わせするためにストアドプロシージャを使う
17.3. 作成、更新、削除のためのカスタム SQL
17.4. ロードのためのカスタム SQL

データベースのネイティブ 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)

このクエリで指定されているものを下記に示します:

これはまだ 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);

このクエリで指定されているものを下記に示します:

Cat が ID 、 NAME 、 BIRTHDATE のカラムを使ってクラスにマッピングされる場合、上記のクエリはどちらも、要素が Cat エンティティであるリストを返します。

エンティティを別のエンティティに 多対一 でマッピングしている場合は、ネイティブクエリを実行する際に、この別のエンティティを返すことも要求します。さもなければ、データベース固有の「column not found(カラムが見つかりません)」エラーが発生します。 * 表記を使用した際は、追加のカラムが自動的に返されますが、次の例のように、 Dog多対一 であることを明示することを私たちは好みます。

sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);

これにより cat.getDog() が正しく機能します。

ここまでは、リザルトセットのカラム名は、マッピングドキュメントで指定されたカラム名と同じであると仮定していました。複数のテーブルが同じカラム名を持つ場合があるため、複数テーブルを結合する 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)

このクエリで指定されているものを下記に示します:

上記で使用している {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()

名前付き 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 には下記のルールが適用されます:

Sybase と MS SQL サーバーに適用されるルールを下記に示します:

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
>