JPQLおよびネイティブSQLを使用したJPAエンティティのクエリ

サンプルアプリケーション

この記事で説明したコードスニペットは、この記事に付属のサ サンプル-アーカイブを見ると、これはJavaサーブレットとJava Persistence APIテクノロジーに基づいた単純なWebアプリケーションであることがわかります。 簡単にするために、エンタープライズbeanを使用せず、サーブレット内から直接JPQLクエリを発行します。 ただし、ここで説明するJPQLクエリをエンタープライズbeanで利用できないという意味ではありません。

図1は、サンプルエンティティ構造を示しています。 あなたが見ることができるように、それは異なるタイプの関係を持つ互いに関連するエンティティのセットが含まれています。 このようなブランチ構造は、記事の後半の”JPQL結合の定義”セクションで説明するJPQL結合クエリの使用方法を説明するために必要です。

図1サンプルアプリケーション

内で使用されるエンティティ間の関係サンプルアプリのセットアップと起動の詳細については、readmeを参照してくださサンプルアーカイブのルートディレクトリにあるtxtファイル。

Java EEアプリケーションでのJPQLの使用

データベースに関する実践的な経験があれば、リレーショナルデータベースの情報にアクセスして操作するための文のセットを提供する標準ツールであるSQLにすでに足を濡らしている可能性が最も高い。 実際、JPQLとSQLの間には多くの類似点があります。 両方とも、長期的には、データベースデータにアクセスして操作するために使用されます。 そして、両方とも、特別なインタプリタによって認識される非専門的な文—コマンドを使用します。 さらに、JPQLはその構文においてSQLに似ています。

JPQLとSQLの主な違いは、前者はJPAエンティティを扱い、後者はリレーショナルデータを直接扱うことにあります。 Java開発者として、SQL/JDBCとは異なり、JPQLを使用すると、JavaコードからJDBC APIを使用する必要がなくなることを学ぶことに興味があるかもしれません。JPQLでは、SELECT、UPDATEまたはDELETEの3つの文のいずれかを使用して問合せを定義できます。 EntityManager APIインターフェイスには、エンティティに対する取得、更新、削除の操作を実行するためにも使用できるメソッドが用意されていることに注意してく 特に、これらはfind、merge、およびremoveメソッドです。 ただし、これらのメソッドの使用は、カスケードが有効にならない限り、通常は単一のエンティティインスタンスに制限されます。 エンティティのセットに対して一括更新操作と削除操作を定義したり、エンティティインスタンスのセットを返すクエリを定義したりできます。JAVAコード内からJPQLクエリを発行するには、EntityManager APIおよびQuery APIの適切なメソッドを使用して、次の一般的な手順を実行する必要があります:

  • 1. インジェクションを使用するか、EntityManagerFactoryインスタンスを使用して明示的にEntityManagerのインスタンスを取得します。
  • 2. CreateQueryなどの適切なEntityManagerのメソッドを呼び出して、Queryのインスタンスを作成します。
  • 3. 適切なクエリのsetParameterメソッドを使用して、クエリパラメータまたはパラメータがある場合は、そのパラメータを設定します。
  • 4. 必要に応じて、setMaxResultsおよび/またはsetFirstResultクエリのメソッドを使用して、取得するインスタンスの最大数を設定したり、取得する最初のインスタンスの位置を指
  • 5. 必要に応じて、setHintクエリのメソッドを使用して、ベンダー固有のヒントを設定します。
  • 6. 必要に応じて、setflushmodeクエリのメソッドを使用してクエリ実行のフラッシュモードを設定し、entity managerのフラッシュモードをオーバーライドします。
  • 7. 適切なクエリのメソッドgetSingleResultまたはgetResultListを使用してクエリを実行します。 ただし、更新または削除操作の場合は、更新または削除されたエンティティインスタンスの数を返すexecuteUpdateメソッドを使用する必要があります。

EntityManagerインタフェース-メソッドとQuery APIインタフェース-メソッドの完全なリストは、Jsr-220の一部であるEnterprise JavaBeans3.0Specification:Java Persistence APIドキュメントに記載されています。

JPQLクエリを作成して発行する方法の大まかなアイデアが得られたので、いくつかの実用的な例を見たいと思うかもしれません。 次のコードフラグメントは、JPQLクエリを使用して、クエリで指定されたCustomerエンティティに関連付けられた基になるリレーショナルテーブルに格納されてい

  ... @PersistenceUnit private EntityManagerFactory emf; public void doGet( ... EntityManager em = emf.createEntityManager(); PrintWriter out = response.getWriter(); List<Customer> arr_cust = (List<Customer>)em.createQuery("SELECT c FROM Customer c") .getResultList(); out.println("List of all customers: "+""); Iterator i = arr_cust.iterator(); Customer cust; while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_id()+""); out.println(cust.getCust_name()+""); out.println(cust.getEmail()+""); out.println(cust.getPhone()+""); out.println("----------------" + ""); } ... 

ここでは、EntityManagerインスタンスのcreateQueryメソッドとクエリインスタンスのgetResultListメソッドが特に関心を集めています。 EntityManagerのcreateQueryは、getResultListメソッドを使用してパラメータとしてcreateQueryに渡されたJPQLクエリを実行するクエリインスタンスを作成するために使用されます。 ご想像のとおり、クエリのgetResultListメソッドは、クエリの結果を、この特定の例では要素がCustomer型にキャストされているリストとして返します。

単一の結果を取得する必要がある場合、クエリAPIインターフェイスには、次の例に示すようにgetSingleResultメソッドが用意されています。 ただし、getSingleResultを使用すると、複数の結果が返された場合に例外が発生することに注意してください。

また、この例では、クエリパラメーターに引数をバインドできるクエリのsetParameterメソッドの使用方法を示しています。 SetParameterを使用すると、名前付きパラメータと位置パラメータの両方をバインドできます。 ただし、ここでは、名前付きパラメータをバインドします。

  ... Integer cust_id =2; Customer cust = (Customer)em.createQuery("SELECT c FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust.getCust_id()+" is: "+ cust.getCust_name()+""); ... 

単一のエンティティインスタンスを取得する場合、SELECT JPQLステートメントを使用することは唯一の方法ではないことに注意するのは興味深いことです。 または、EntityManagerのfindメソッドを使用すると、パラメータとして渡されたエンティティのidに基づいて単一のエンティティインスタンスを取得できます。

状況によっては、ターゲットエンティティインスタンスから一部の情報のみを取得し、特定のエンティティフィールドに対するJPQLクエリを定義する必要 ここで照会されたCustomerエンティティインスタンスのcust_nameフィールドの値のみを取得する必要がある場合、上記のスニペットは次のようになります:

  ... Integer cust_id =2; String cust_name = (String)em.createQuery("SELECT c.cust_name FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust_id+" is: "+cust_name+""); ... 

同様に、顧客の名前のリスト全体を取得するには、次のコードを使用します:

  ... List<String> arr_cust_name = (List<String>)em.createQuery("SELECT c.cust_name FROM Customer c") .getResultList(); out.println("List of all customers: "+"<br/>"); Iterator i = arr_cust_name.iterator(); String cust_name; while (i.hasNext()) { cust_name = (String) i.next(); out.println(cust_name+"<br/>"); } ... 

SQLに戻ると、SQLクエリのselectリストは、FROM句で指定されたテーブルの複数のフィールドで構成できることを思い出すかもしれません。 JPQLでは、対象のエンティティフィールドからのみデータを選択する、構成された選択リストを使用することもできます。 ただし、その場合は、クエリ結果をキャストするクラスを作成する必要があります。 次のセクションでは、選択リストが複数のエンティティから派生したフィールドで構成されているJPQL結合クエリの例を表示します。

JPQL結合の定義

SQLと同様に、JPQLでは結合クエリを定義できます。 ただし、SQLでは、通常、結合クエリのselectリスト内のこれらのテーブルおよびビューの必須フィールドのみを含む、複数のテーブルおよびビューのレコードを結合する結合を定義します。 対照的に、JPQL結合クエリの選択リストには、通常、単一のエンティティまたは単一のエンティティフィールドが含まれます。 その理由は、JPA技術の性質にあります。 エンティティインスタンスを取得したら、対応するgetterメソッドを使用して関連するインスタンスに移動できます。 この方法では、関心のある関連するすべてのエンティティインスタンスを一度に返すクエリを定義する必要はありません。

たとえば、SQLで注文に関する情報とその明細を取得するには、purchaseOrdersテーブルとorderLineItemsテーブルの両方に結合クエリを定義し、クエリの選択リスト内の両方のテーブ ただし、JPQLを使用する場合は、PurchaseOrderエンティティに対してのみクエリを定義し、必要に応じてPurchaseOrderのgetOrderLineItemsメソッドを使用して対応するOrderLineItemインスタンスに移動 この例では、取得したPurchaseOrderインスタンスをOrderLineItemに適用された条件に基づいてフィルタリングする必要がある場合にのみ、PurchaseOrderエンティティとOrderLineItemエンティティ

次のスニペットは、動作中のJPQL joinクエリの例を示しています。 関係するエンティティが互いにどのように関連しているかをよりよく理解するには、この記事の前のサンプルアプリケーションのセクションに示す図1に戻ることができます。

  ... Double max = (Double) em.createQuery("SELECT MAX(p.price) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getSingleResult(); out.println("The highest price for an ordered product supplied by Tortuga Trading: "+ max + "<br/>"); ... 

上記の例では、結合クエリのSELECT句でMAX集計関数を使用して、Tortuga Tradingによって提供され、少なくとも1回注文された商品の最高価格の商品を決定します。

しかし、より一般的な状況は、たとえば、特定のサプライヤーによって供給された注文された製品の合計価格を計算する必要がある場合です。 これは、SUM集計関数が便利な場所です。 SQLでは、このような結合クエリは次のようになります:

  SELECT SUM(p.price*l.quantity) FROM purchaseorders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name ='Tortuga Trading'; 

残念ながら、JPQLで使用されているSUM関数では、算術式を引数として渡すことはできません。 これが実際に意味することは、JPQLのSUMの引数としてp.price*l.quantityを渡すことができないということです。 ただし、この問題を回避する方法があります。 次の例では、クラスLineItemSumを定義し、そのコンストラクターをクエリの選択リストで使用し、p.priceとl.quantityをパラメーターとして使用します。 LineItemSumコンストラクタが行うことは、p.priceにl.quantityを掛け、結果をrsltクラス変数に保存することです。 次に、クエリによって取得されたLineItemSumリストを反復処理し、LineItemSumのrslt変数の値を合計することができます。 次のスニペットは、これらすべてをコードでどのように実装できるかを示しています:

  package jpqlexample.servlets; ... class LineItemSum { private Double price; private Integer quantity; private Double rslt; public LineItemSum (Double price, Integer quantity){ this.rslt = quantity*price; } public Double getRslt () { return this.rslt; } public void setRslt (Double rslt) { this.rslt = rslt; } } public class JpqlJoinsServlet extends HttpServlet { ... public void doGet( ... List<LineItemSum> arr = (List<LineItemSum>)em.createQuery ("SELECT NEW jpqlexample.servlets.LineItemSum(p.price, l.quantity) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getResultList(); Iterator i = arr.iterator(); LineItemSum lineItemSum; Double sum = 0.0; while (i.hasNext()) { lineItemSum = (LineItemSum) i.next(); sum = sum + lineItemSum.getRslt(); } out.println("The total cost of the ordered products supplied by Tortuga Trading: "+ sum + "<br/>"); } } 

とりわけ、上記の例では、複数のエンティティから派生したフィールドを含むJPQLクエリのselectリストで、エンティティクラスではなくカスタムJavaクラ ただし、ほとんどの場合、特定のエンティティのインスタンスまたはインスタンスのリストを受け取るクエリを処理する必要があります。

取得されたエンティティインスタンスと現在の永続コンテキスト

これまでの記事の例のクエリ結果は単純に印刷されています。 ただし、実際のアプリケーションでは、クエリ結果に対してさらにいくつかの操作を実行する必要がある場合があります。 たとえば、取得したインスタンスを更新してから、データベースに永続化する必要がある場合があります。 これは、JPQLクエリによって取得されるインスタンスは、アプリケーションによってさらに処理される準備ができているか、またはその準備をするた 特に、現在の永続コンテキストに関して、取得されたエンティティインスタンスがどのような状態であるかを学ぶことは興味深

Javaの永続性に関する経験がある場合は、永続性コンテキストが何であるかを知っておく必要があります。 要約すると、永続コンテキストは、そのコンテキストに関連付けられたEntityManagerインスタンスによって管理されるエンティティインスタンスのセッ 前の例では、EntityManagerのcreateQueryメソッドを使用して、JPQLクエリを実行するためのQueryのインスタンスを作成しました。 実際には、EntityManager APIには、エンティティインスタンスのライフサイクルを管理し、トランザクションを制御し、クエリのインスタンスを作成するための

永続コンテキストに関して、エンティティインスタンスは、new、managed、detached、またはremovedの四つの状態のいずれかになります。 適切なEntityManagerのメソッドを使用して、必要に応じて特定のエンティティインスタンスの状態を変更できます。 ただし、データベースへのフラッシュが発生したときに、管理状態のインスタンスのみがデータベースに同期されることに注意してください。 正確には、removed状態のエンティティインスタンスも同期され、これらのインスタンスに対応するデータベースレコードがデータベースから削除されます。

対照的に、newまたはdetached状態のインスタンスはデータベースに同期されません。 たとえば、新しいPurchaseOrderインスタンスを作成し、EntityManagerのflushメソッドを呼び出すと、PurchaseOrderエンティティがマップされているpurchaseOrdersテーブルに別のレコードは表示されません。 これは、新しいPurchaseOrderインスタンスが永続コンテキストにアタッチされていないためです。 コードは次のようになります:

  ... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.getTransaction().commit(); ... 

この問題を解決するには、次の例に示すように、flushを呼び出す前に、新しいPurchaseOrderインスタンスのEntityManagerのpersistメソッドを呼び出す必要があります:

  ... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.persist(ord); em.getTransaction().commit(); ... 

または、CustomerエンティティでPurchaseOrderとの関係を定義するときにcascadeオプションをPERSISTまたはALLに設定している場合は、新しく作成されたPurchaseOrderインスタンスをcustomerイ:

  cust.getPurchaseOrders().add(ord); 

エンティティインスタンスの状態に関する上記の説明では、JPQLクエリによって取得されたエンティティインスタンスが自動的に管理されるか、ま JPA仕様によれば、EntityManagerのfindメソッドであろうとクエリであろうと、エンティティを取得する方法に関係なく、現在の永続性コンテキストに自動的にアタッチ これは、JPQLクエリによって取得されたエンティティインスタンスが自動的に管理されることを意味します。 たとえば、取得したインスタンスのフィールドの値を変更し、EntityManagerのflushメソッドを呼び出すか、現在のトランザクションをコミットすることで、その変更をデー 取得したインスタンスに関連付けられたインスタンスの状態についても心配する必要はありません。 実際には、関連するインスタンスに初めてアクセスすると、自動的に管理されます。 これが実際にどのように機能するかを示す簡単な例を次に示します:

  ... em.getTransaction().begin(); PurchaseOrder ord = (PurchaseOrder)em.createQuery("SELECT o FROM PurchaseOrder o WHERE o.pono = 1") .getSingleResult(); List<OrderLineItem> items = ord.getOrderLineItems(); Integer qnt = items.get(0).getQuantity(); out.println("Quantity of the first item : "+ qnt +"<br/>"); items.get(0).setQuantity(qnt+1); qnt = items.get(0).getQuantity(); em.getTransaction().commit(); out.println("Quantity of the first item : "+ qnt +"<br/>"); ... 

取得したPurchaseOrderインスタンスのpersistメソッドや、ここで変更される関連するOrderLineItemインスタンスのpersistメソッドは呼び出さないことに注意してください。 この事実にもかかわらず、注文の最初の行項目に加えられた変更は、トランザクションをコミットするときにデータベースに保持されます。 これは、取得されたエンティティインスタンスとその関連付けの両方が自動的に現在の永続化コンテキストにアタッチされるために発生します。 前述したように、前者は取得されると管理され、後者はアクセスするとコンテキストにアタッチされます。

状況によっては、最初のアクセスではなく、クエリの実行時に関連付けをコンテキストにアタッチすることができます。 これは、フェッチ結合が便利な場所です。 たとえば、その顧客インスタンスを取得するときに、特定の顧客に属するすべての注文を取得したいとします。 この方法では、クエリの実行時に利用可能な顧客注文を処理することが保証されます。 たとえば、新しい注文が別のコンテキストに追加され、取得されたcustomerインスタンスに関連付けられた注文リストに初めてアクセスする前にデータベースに同期された場合、データベースからcustomerインスタンスの状態を更新するまで、この変更は表示されません。 次のスニペットでは、cust_idが1のCustomerインスタンスを返し、取得するCustomerインスタンスに関連付けられたPurchaseOrderインスタンスをフェッチするjoinクエリを使

  ... Customer cust = (Customer)em.createQuery("SELECT DISTINCT c FROM Customer c LEFT JOIN FETCH c.purchaseOrders WHERE c.cust_id=1") .getSingleResult(); ... List<PurchaseOrder> orders = cust.getPurchaseOrders(); ... 

明示的なクエリ結果の一部ではないため、ここで取得されたCustomerインスタンスに関連付けられたPurchaseOrderエンティティインスタンスも取得され、クエリの実

ネイティブSQLクエリの利用

クエリAPIで実行されるクエリを定義するときは、JPQLに限定されないことに注意するのは興味深いことです。 EntityManager APIには、ネイティブSQLステートメントを実行するためのクエリのインスタンスを作成するメソッドが用意されていることに驚くかもしれません。 EntityManagerメソッドを使用して作成されたネイティブSQLクエリについて理解する最も重要なことは、JPQLクエリのように、データベーステーブルレコードではなくエンティテ 動的なネイティブSQLクエリの簡単な例を次に示します:

  ... List<Customer> customers = (List<Customer>)em.createNativeQuery ("SELECT * FROM customers", jpqlexample.entities.Customer.class) .getResultList(); Iterator i = customers.iterator(); Customer cust; out.println("Customers: " + "<br/>"); while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_name() +"<br/>"); } ... 

JPQLはまだ進化しており、SQLで利用できるこれらの重要な機能の多くはありません。 JPQLのSUM集計関数は算術式をパラメータとして取ることができないため、多くの作業を自分で行う必要がありました。 対照的に、SQLのSUM関数にはそのような制限はありません。 したがって、これは、JPQLをネイティブSQLに置き換えることが効率的であることの良い例です。 次のコードは、JPQLを使用してネイティブSQLを選択することで、この特定の例の処理を簡素化する方法を示しています:

  ... String sup_name ="Tortuga Trading"; BigDecimal sum = (List)em.createNativeQuery("SELECT SUM(p.price*l.quantity) FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name =?1") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ... 

上記の例では、引数をネイティブクエリパラメータにバインドできることを示しています。 特に、JPQLクエリを処理する場合と同じ方法で、引数を位置パラメータにバインドできます。

ネイティブクエリの主な欠点は、結果バインディングの複雑さです。 この例では、クエリは単純な型の単一の結果を生成するため、この問題は回避されます。 ただし、実際には、複雑な型の結果セットを処理する必要があることがよくあります。 この場合、ネイティブクエリをマップできるエンティティを宣言するか、複数のエンティティまたはエンティティとスカラー結果のブレンドにマップされた複雑な結果セットを定義する必要があります。

ストアドプロシージャの使用

ネイティブクエリのもう一つの欠点は、Javaコードが基になるデータベース構造に直接依存することです。 その基礎となる構造を変更する場合は、サーブレットや他のアプリケーションコンポーネントに関係するネイティブクエリを調整し、その後にそれらのコン この問題を回避するには、ネイティブクエリを使用しながら、ストアドプロシージャを利用して、複雑なSQLクエリをデータベース内に格納および実行され これが実際に意味することは、ストアドプロシージャが、Javaコードでハードコードされているクエリから直接基になるテーブルを処理する手間を省くことがで このアプローチの利点は、ほとんどの場合、基になるデータベース構造の変更に従うためにJavaコードを変更する必要がないことです。 代わりに、修正が必要なのはストアドプロシージャだけです。

前のセクションで説明した例に戻ると、そこで使用されている複合結合クエリを、次のように作成されたストアドプロシージャに移動できます:

  CREATE OR REPLACE FUNCTION sum_total(supplier VARCHAR2) RETURN NUMBER AS sup_sum NUMBER; BEGIN SELECT SUM(p.price*l.quantity) INTO sup_sum FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name = supplier; RETURN sup_sum; END; / 

これにより、Javaコードで使用されるネイティブクエリが簡素化され、基になるテーブルから依存関係が削除されます:

  ... String sup_name ="Tortuga Trading"; BigDecimal sum = (BigDecimal)em.createNativeQuery("SELECT sum_total(?1) FROM DUAL") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ... 

結論

この記事で学んだように、JPQLはJava Persistenceを利用してJavaアプリケーション内からリレーショナルデータにアクセスするための強力なツールです。 JPQLでは、SQL/JDBCとは異なり、それらのテーブルを直接照会するのではなく、基になるデータベーステーブルにマップされたJPAエンティティに対する照会を定義 また、JPAエンティティを介してクエリを作成する場合は、JPQLが唯一のオプションではないことも学びました。