JPAクエリパラメータの使用法
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でから入手できます。