1. 序章

JPAを使用してクエリを作成することは難しくありません。 ただし、大きな違いを生む単純なことを忘れることがあります。

これらの1つはJPAクエリパラメータであり、これがこのチュートリアルで焦点を当てるものです。

2. クエリパラメータとは何ですか?

クエリパラメータとは何かを説明することから始めましょう。

クエリパラメータは、パラメータ化されたクエリを作成して実行する方法です。 したがって、代わりに:

SELECT * FROM employees e WHERE e.emp_number = '123';

私たちはします:

SELECT * FROM employees e WHERE e.emp_number = ?;

JDBCプリペアドステートメントを使用して、クエリを実行する前にパラメータを設定する必要があります。

pStatement.setString(1, 123);

3. クエリパラメータを使用する必要があるのはなぜですか?

クエリパラメータを使用する代わりに、リテラルを使用することもできますが、これから説明するように、これは推奨される方法ではありません。

JPAAPIを使用してemp_numberで従業員を取得するように前のクエリを書き直してみましょう。ただし、パラメータを使用する代わりに、リテラルを使用して状況を明確に示します。

String empNumber = "A123";
TypedQuery<Employee> query = em.createQuery(
  "SELECT e FROM Employee e WHERE e.empNumber = '" + empNumber + "'", Employee.class);
Employee employee = query.getSingleResult();

このアプローチにはいくつかの欠点があります。

  • パラメータを埋め込むとセキュリティリスクが発生し、JPQLインジェクション攻撃に対して脆弱になります。 攻撃者は、期待値の代わりに、予期しない、場合によっては危険なJPQL式を挿入する可能性があります。
  • 使用するJPA実装、およびアプリケーションのヒューリスティックによっては、クエリキャッシュが使い果たされる可能性があります。 新しいクエリは、新しい値/パラメータで使用するたびに、ビルド、コンパイル、およびキャッシュされる場合があります。 少なくとも、効率的ではなく、予期しないOutOfMemoryErrorが発生する可能性もあります。

4. JPAクエリパラメータ

JDBCプリペアドステートメントパラメータと同様に、JPAは、以下を使用してパラメータ化されたクエリを作成する2つの異なる方法を指定します。

  • 位置パラメータ
  • 名前付きパラメーター

位置パラメータまたは名前付きパラメータのいずれかを使用できますが、同じクエリ内でそれらを混在させてはなりません。

4.1. 位置パラメータ

位置パラメータを使用することは、前述の問題を回避する1つの方法です。

位置パラメータを使用してこのようなクエリを作成する方法を見てみましょう。

TypedQuery<Employee> query = em.createQuery(
  "SELECT e FROM Employee e WHERE e.empNumber = ?1", Employee.class);
String empNumber = "A123";
Employee employee = query.setParameter(1, empNumber).getSingleResult();

前の例で見たように、疑問符を入力し、その後に正の整数を入力して、クエリ内でこれらのパラメーターを宣言します。 1 から始めて、毎回1ずつ増やしていきます。

同じクエリ内で同じパラメータを複数回使用する場合があります。これにより、これらのパラメータは名前付きパラメータにより類似したものになります。

パラメータの番号付けは、使いやすさ、読みやすさ、およびメンテナンスを向上させるため、非常に便利な機能です。

ネイティブSQLクエリは位置パラメータバインディングもサポートしていることは言及する価値があります。

4.2. コレクション値の位置パラメータ

前述のように、コレクション値のパラメーターを使用することもできます。

TypedQuery<Employee> query = entityManager.createQuery(
  "SELECT e FROM Employee e WHERE e.empNumber IN (?1)" , Employee.class);
List<String> empNumbers = Arrays.asList("A123", "A124");
List<Employee> employees = query.setParameter(1, empNumbers).getResultList();

4.3. 名前付きパラメーター

名前付きパラメーターは、位置パラメーターと非常によく似ています。 ただし、それらを使用することで、パラメーターがより明確になり、クエリが読みやすくなります。

TypedQuery<Employee> query = em.createQuery(
  "SELECT e FROM Employee e WHERE e.empNumber = :number" , Employee.class);
String empNumber = "A123";
Employee employee = query.setParameter("number", empNumber).getSingleResult();

前のサンプルクエリは最初のクエリと同じですが、?1 の代わりに、名前付きパラメーターである:numberを使用しました。

コロンを使用してパラメーターを宣言し、その後に文字列識別子(JPQL識別子)を指定したことがわかります。これは、実行時に設定する実際の値のプレースホルダーです。 クエリを実行する前に、setParameterメソッドを発行して1つまたは複数のパラメーターを設定する必要があります。

注目すべき興味深い点の1つは、 TypedQueryがメソッドチェーンをサポートしていることです。これは、複数のパラメーターを設定する必要がある場合に非常に役立ちます。

先に進み、2つの名前付きパラメーターを使用して前のクエリのバリエーションを作成し、メソッドの連鎖を説明しましょう。

TypedQuery<Employee> query = em.createQuery(
  "SELECT e FROM Employee e WHERE e.name = :name AND e.age = :empAge" , Employee.class);
String empName = "John Doe";
int empAge = 55;
List<Employee> employees = query
  .setParameter("name", empName)
  .setParameter("empAge", empAge)
  .getResultList();

ここでは、指定された名前と年齢のすべての従業員を取得しています。 はっきりとわかるように、また予想されるように、複数のパラメーターと必要な数のパラメーターを使用してクエリを作成できます。

何らかの理由で同じクエリ内で同じパラメータを何度も使用する必要がある場合は、「setParameter」メソッドを発行して一度だけ設定する必要があります。 実行時に、指定された値がパラメーターの各出現箇所を置き換えます。

最後に、 Java Persistence API仕様では、ネイティブクエリが名前付きパラメーターをサポートすることを義務付けていないことに言及する価値があります。 Hibernateのような一部の実装がそれをサポートしている場合でも、それを使用する場合、クエリはそれほど移植性がないことを考慮する必要があります。

4.4. コレクション値の名前付きパラメーター

わかりやすくするために、これがコレクション値パラメーターでどのように機能するかについても説明します。

TypedQuery<Employee> query = entityManager.createQuery(
  "SELECT e FROM Employee e WHERE e.empNumber IN (:numbers)" , Employee.class);
List<String> empNumbers = Arrays.asList("A123", "A124");
List<Employee> employees = query.setParameter("numbers", empNumbers).getResultList();

ご覧のとおり、位置パラメータと同じように機能します。

5. 基準クエリパラメータ

JPAクエリは、 JPA Criteria API を使用して構築できます。これは、Hibernateの公式ドキュメントで詳細に説明されています。

このタイプのクエリでは、名前やインデックスの代わりにオブジェクトを使用してパラメータを表します。

同じクエリをもう一度作成しましょう。ただし、今回はCriteria APIを使用して、CriteriaQueryを処理するときにクエリパラメーターを処理する方法を示します。

CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<Employee> cQuery = cb.createQuery(Employee.class);
Root<Employee> c = cQuery.from(Employee.class);
ParameterExpression<String> paramEmpNumber = cb.parameter(String.class);
cQuery.select(c).where(cb.equal(c.get(Employee_.empNumber), paramEmpNumber));

TypedQuery<Employee> query = em.createQuery(cQuery);
String empNumber = "A123";
query.setParameter(paramEmpNumber, empNumber);
Employee employee = query.getResultList();

このタイプのクエリでは、パラメータオブジェクトを使用するため、パラメータの仕組みは少し異なりますが、本質的には違いはありません。

前の例では、Employee_クラスの使用法を確認できます。 このクラスは、Hibernateメタモデルジェネレーターを使用して生成しました。 これらのコンポーネントは静的JPAメタモデルの一部であり、厳密に型指定された方法で基準クエリを作成できます。

6. 結論

この記事では、JPAクエリパラメーターまたは入力パラメーターを使用してクエリを構築するメカニズムに焦点を当てました。

位置と名前の2種類のクエリパラメータがあることを学びました。どちらが目的に最も適しているかは、私たち次第です。

in 式を除いて、すべてのクエリパラメータは単一値でなければならないことにも注意してください。 in 式の場合、前の例に示すように、配列やListなどのコレクション値の入力パラメーターを使用できます。

いつものように、この記事のソースコードはGitHubから入手できます。