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. クエリパラメータを使用する理由

ただし、クエリパラメーターを使用する代わりに、リテラルを使用することもできますが、これは、これから説明するように推奨される方法ではありません。
前のクエリを書き換えて、JPA APIを使用して_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ずつ増やしていきます。
同じクエリ内で同じパラメーターを複数回使用すると、これらのパラメーターが名前付きパラメーターにより似たものになります。
パラメータの番号付けは、使いやすさ、読みやすさ、およびメンテナンスを改善するため、非常に便利な機能です。
ただし、JPA仕様に従って、この機能をネイティブクエリで安全に使用することはできません。仕様では強制されていないためです。

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_ methodを発行してパラメーターを設定する必要があります。
注目すべき興味深い点の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クエリは、https://www.baeldung.com/hibernate-criteria-queries-metamodel [JPA基準API]を使用して構築できます。これは、https://docs.jboss.org/hibernate/orm/5.2/topical /html_single/metamodelgen/MetamodelGenerator.html[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__ classの使用法を確認できます。 Hibernateメタモデルジェネレーターでこのクラスを生成しました。 これらのコンポーネントは静的JPAメタモデルの一部であり、厳密に型指定された方法で条件クエリを構築できます。

6. 結論

この記事では、JPAクエリパラメーターまたは入力パラメーターを使用してクエリを作成するメカニズムに焦点を当てました。
位置と名前の2種類のクエリパラメーターがあることを学びました。 どれが私たちの目的に最も合うかは私たち次第です。
また、_in_式を除き、すべてのクエリパラメーターは単一値でなければならないことに注意してください。 _in_式では、前の例で示したように、配列や__List__sなどのコレクション値の入力パラメーターを使用できます。
このチュートリアルのソースコードは、通常どおり、https://github.com/tryncatch/tutorials/tree/master/persistence-modules/java-jpa-2 [GitHubで入手可能]です。