JavaEE7をはじめよう(5) - JPAクエリ(その2) Native Query
前回の記事ではJPAのクエリの定義方法のうち、JPQLについて解説した。
今回はNative Queryについて解説する。
Native Queryとは
Native Queryは直接SQLを実行する機能であり、JPQLではサポートされてないSQLやデータベース製品固有の方言などを実行することができる。
JPQLでサポートされてない構文には以下のようなものがある。
ただしNative Queryは、JPQLで実現できない機能を利用する場合や、JPQLが生成するSQLが非効率な場合のみに使用するべきであり、JPQLがSQLに翻訳するコストがかかるという理由で使用するべきではない。 これは、JPQLからSQLへの翻訳コストは無視可能なほど低いことに加えて、後述するようにNative QueryはJPQLに比べて不便な所が多いからである。
定義・使用方法
JPQLと似ている部分があるので、異なる部分を中心に解説する。
JPQL同様に、静的・動的の2種類の定義方法がある。
- NamedNativeQuery(名前つきネイティブクエリ)による定義
- 文字列による定義
NamedNativeQueryの使用方法
NamedNativeQuery はアノテーションの@NamedNativeQuery
を用いて指定する。
@NamedQuery
と異なる部分は、結果のマッピングに使用するプロパティ resultClass
, resultSetMapping
がある点だ。
詳細は後述するが、resultClass
はクエリの結果を格納するエンティティクラスを指定するプロパティであり、resultSetMapping
はクエリの結果をオブジェクトにマッピングする定義の名称を記載するプロパティである。
両者のうち、いずれかを設定する必要がある。
以下にNamedNativeQueryを使ったコード例を示す。
ここではMember.getNumber
という名前をつけたNaitive Queryに対して、クエリ結果をMember
オブジェクトとして受け取るため、resultClass
プロパティにMember.class
オブジェクトを指定している。
@Entity @Table(uniqueConstraints = @UniqueConstraint( columnNames = {"PLAYER_NUMBER", "belongs_id"})) @NamedNativeQueries({ @NamedNativeQuery(name = "Member.getNumber", query = "Select id, PLAYER_NUMBER from member", resultClass = Member.class) }) public class Member implements Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; /** 背番号 */ @Column(name="PLAYER_NUMBER",scale = 4) private int playerNumber;
実行する場合は、JPQL同様にEntityManager#createNamedQuery
を使用する。
List<Member> list
= em.createNamedQuery("Member.getNumber").getResultList();
文字列による定義の使用方法
文字列ベースで指定する場合は、EntityManager#createNativeQuery
を使用する。その場合、第二引数には、resultClass
またはresultSetMapping
名を指定する。これは、@NamedNativeQuery
にあるプロパティと同じものだ。
List<Member> list = em.createNativeQuery("Select id, PLAYER_NUMBER from member", Member.class) .getResultList();
クエリ作成後の操作はJPQLの場合と同じである。
結果のマッピング
Native Queryの結果を任意のオブジェクトにマッピングするには、resultClass
またはresultSetMapping
を使用する。
これらを指定しない場合、select
句が単一の場合はObject
が、複数の場合はObject
配列が結果の型となる。
ちなみに、先ほど説明したように、resultClass
やresultSetMapping
は、NamedNativeQuery(名前つきネイティブクエリ)を利用する場合にはアノテーションのプロパティとして指定し、文字列による定義を利用する場合にはcreateNamedQuery
メソッドの第2引数として指定する。
resultClass
resultClass
に指定可能なのは、上記のサンプルにあるとおり、エンティティのclass
オブジェクトのみである。
JPAはselect
句に指定した列から、エンティティのインスタンスを作成し、永続性コンテキストに管理状態として登録する。
そのため、select
句にはエンティティクラスのIDを設定した列(ここではid
)を必ず設定する必要がある。また、select
句で指定されなかった列に対応するエンティティのプロパティにはnull
が設定される。
resultSetMapping
resultSetMapping
は任意のクラス構造に結果を設定する方法であり、JPA2.1より追加された。
これを利用することで、複数のエンティティのマッピングや、列名とプロパティ名が異なる場合のマッピング、エンティティ以外のクラスへのマッピングなど、様々なマッピングが可能となる。
マッピング内容はアノテーションとして指定し、クエリ生成時にそのマッピング名を指定する。
ここでは、前回の記事の後半で、コンストラクタ式を解説する際に紹介した、チームごとの人数を取得する例をこの機能を使用して実現する。
まず、SQLのselect
句からオブジェクトへのマッピング内容を定義するための@SqlResultSetMapping
アノテーションを設定する。
@Entity @Table(uniqueConstraints = @UniqueConstraint(columnNames = "name")) @SqlResultSetMappings({ @SqlResultSetMapping(name="summary", classes = @ConstructorResult(targetClass = TeamSummary.class, columns = {@ColumnResult(name = "name"), @ColumnResult(name = "count")})) }) public class Team implements Serializable { //....
上記の例では、summary
という名前のマッピングを定義し、その中で、クエリ実行時にTeamSummary
クラスのコンストラクタを呼び出しての引数にselect
句のname
, count
の値を順に渡すことを定義している。
このマッピングを使用するには、createNativeQuery
メソッドの呼び出し時にマッピング名(@SqlResultSetMapping
のname
属性)を指定する。
List<TeamSummary> summary2 = em.createNativeQuery( "Select t.name as name, count(*) as count" + " from Member m join Team t on m.belongs_id = t.id" + " group by t.id, t.name", "summary" ).getResultList();
Native Queryの注意点
JPQL と比較したNative Queryを扱う際の注意点を挙げる。
テーブル名、列名は実際の名前を使用する必要がある
JPAでは、エンティティクラスに指定するアノテーションにより、実際のテーブル名や列名と異なる名前をプロパティ名に指定できる。
前回説明したJPQLでは、クエリにエンティティのプロパティ名を指定したが、NativeQueryを利用する場合は実際の列名を指定する必要がある。バインド変数にはインデックス指定 (
?n
)のみが使用でき、 文字列指定は使えない。JPQLの便利な機能を利用できない
JPQLの場合にはQuery#setFirstResult
やQuery#setMaxResults
を実行すると、 データベース製品にあわせた件数指定のSQLが生成される(例えば、OracleなどLIMIT句,OFFSET句が無い製品であっても、代替となる構文や関数を使用したSQLを自動で生成する)。
しかし、Native Queryで同様の指定をした場合は、SQLはそのまま実行し、結果セットから必要な件数分のデータを取得するように動作する。
このため、大量データから必要なデータを取得してページングするようなクエリでは、全データを取得するSQLが発行されるため、JPQLの場合と比較して実行効率が悪くなる可能性がある。
また、NativeQuery で件数指定を行う場合は、データベース製品にあわせた専用のクエリを別途作成する必要があるため、JPQLを使用する方が移植性が高くなる。
加えて、JPQLの説明で紹介した、List
によるIN
句の設定や関連の自動ナビゲーションなどの便利な機能も利用できない。
まとめ
前回に続いて、今回はJPAのクエリの実行方法として、Native Queryを紹介した。次回は、Criteria APIを紹介する。
[前多 賢太郎]