1. 概要

このチュートリアルでは、さまざまなタイプのJPAクエリについて説明します。 さらに、それらの違いを比較し、それぞれの長所と短所を拡張することに焦点を当てます。

2. 設定

まず、この記事のすべての例で使用するUserEntityクラスを定義しましょう。

@Table(name = "users")
@Entity
public class UserEntity {

    @Id
    private Long id;
    private String name;
    //Standard constructor, getters and setters.

}

JPAクエリには3つの基本的なタイプがあります。

  • Query 、Java Persistence Query Language(JPQL)構文で記述
  • NativeQuery 、プレーンSQL構文で記述
  • Criteria API Query 、さまざまなメソッドを介してプログラムで構築

それらを調べてみましょう。

3. クエリ

Query は、構文がSQLに似ており、通常、CRUD操作を実行するために使用されます。

public UserEntity getUserByIdWithPlainQuery(Long id) {
    Query jpqlQuery = getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id");
    jpqlQuery.setParameter("id", id);
    return (UserEntity) jpqlQuery.getSingleResult();
}

このQueryは、 users テーブルから一致するレコードを取得し、それをUserEntityオブジェクトにマップします。

2つの追加のQueryサブタイプがあります。

  • TypedQuery
  • NamedQuery

それらの動作を見てみましょう。

3.1. TypedQuery

前の例のreturnステートメントに注意を払う必要があります。 JPAは、 Query の結果タイプがどうなるかを推測できないため、キャストする必要があります。

しかし、 JPAは、TypedQueryと呼ばれる特別なQueryサブタイプを提供します。 私たちが知っているなら、これは常に好まれますクエリ事前に結果を入力してください。 さらに、コードの信頼性が大幅に向上し、テストが容易になります。

最初の例と比較して、TypedQueryの代替案を見てみましょう。

public UserEntity getUserByIdWithTypedQuery(Long id) {
    TypedQuery<UserEntity> typedQuery
      = getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id", UserEntity.class);
    typedQuery.setParameter("id", id);
    return typedQuery.getSingleResult();
}

このようにして、無料でより強力なタイピングを取得し、将来のキャスト例外の可能性を回避します。

3.2.  NamedQuery

特定のメソッドでQueryを動的に定義することはできますが、最終的には保守が難しいコードベースに成長する可能性があります。 一般的な使用法のクエリを一元化された読みやすい場所に保持できるとしたらどうでしょうか。

JPAは、NamedQueryと呼ばれる別のQueryサブタイプでもこれをカバーしています。

NamedQueriesorm.xmlまたはプロパティファイルで定義できます。

また、Entityクラス自体にNamedQueryを定義して、Entityの関連クエリを一元化してすばやく簡単に読み取って見つけることができます。

すべてのNamedQueriesには一意の名前が必要です。

NamedQueryUserEntityクラスに追加する方法を見てみましょう。

@Table(name = "users")
@Entity
@NamedQuery(name = "UserEntity.findByUserId", query = "SELECT u FROM UserEntity u WHERE u.id=:userId")
public class UserEntity {

    @Id
    private Long id;
    private String name;
    //Standard constructor, getters and setters.

}

バージョン8より前のJavaを使用している場合は、@NamedQueryアノテーションを@NamedQueriesアノテーション内にグループ化する必要があります。 Java 8以降では、Entityクラスで@NamedQueryアノテーションを繰り返すことができます。

NamedQueryの使用は非常に簡単です。

public UserEntity getUserByIdWithNamedQuery(Long id) {
    Query namedQuery = getEntityManager().createNamedQuery("UserEntity.findByUserId");
    namedQuery.setParameter("userId", id);
    return (UserEntity) namedQuery.getSingleResult();
}

4. NativeQuery

NativeQueryは単なるSQLクエリです。 これらにより、JPQLで制限された構文では利用できない独自の機能を使用できるため、データベースの能力を最大限に引き出すことができます。

これにはコストがかかります。 NativeQuery を使用すると、アプリケーションのデータベースの移植性が失われます。これは、JPAプロバイダーがデータベース実装またはベンダーから特定の詳細を抽象化できなくなったためです。

前の例と同じ結果が得られるNativeQueryの使用方法を見てみましょう。

public UserEntity getUserByIdWithNativeQuery(Long id) {
    Query nativeQuery
      = getEntityManager().createNativeQuery("SELECT * FROM users WHERE id=:userId", UserEntity.class);
    nativeQuery.setParameter("userId", id);
    return (UserEntity) nativeQuery.getSingleResult();
}

NativeQueryが唯一のオプションであるかどうかを常に考慮する必要があります。 ほとんどの場合、優れたJPQLクエリは私たちのニーズを満たし、最も重要なことに、実際のデータベース実装からの抽象化のレベルを維持できます。

NativeQuery を使用することは、必ずしも1つの特定のデータベースベンダーに自分自身を固定することを意味するわけではありません。 結局のところ、クエリが独自のSQLコマンドを使用せず、標準のSQL構文のみを使用している場合は、プロバイダーの切り替えは問題になりません。

5. Query、NamedQuery 、および NativeQuery

これまで、 Query NamedQuery 、およびNativeQueryについて学習してきました。

それでは、すぐに再訪して、長所と短所を要約しましょう。

5.1. クエリ

entityManager.createQuery(queryString)を使用してクエリを作成できます。

次に、クエリの長所と短所を見てみましょう。

長所:

  • EntityManager を使用してクエリを作成すると、動的なクエリ文字列を作成できます
  • クエリはJPQLで記述されているため、移植性があります

短所:

  • 動的クエリの場合、クエリプランキャッシュに応じて、ネイティブSQLステートメントに複数回コンパイルされる場合があります。
  • クエリはさまざまなJavaクラスに分散する可能性があり、Javaコードと混合されます。 したがって、プロジェクトに多くのクエリが含まれている場合、保守が困難になる可能性があります

5.2. NamedQuery

NamedQuery が定義されると、EntityManagerを使用してそれを参照できます。

entityManager.createNamedQuery(queryName);

それでは、NamedQueriesの長所と短所を見てみましょう。

長所:

  • NamedQueries は、永続性ユニットがロードされるときにコンパイルおよび検証されます。 つまり、それらは一度だけコンパイルされます
  • NamedQueries を一元化して、保守を容易にすることができます。たとえば、 orm.xml 、プロパティファイル、@Entityクラスなどです。

短所:

  • NamedQueriesは常に静的です
  • NamedQueries は、SpringDataJPAリポジトリーで参照できます。 ただし、動的並べ替えはサポートされていません

5.3. NativeQuery

EntityManager を使用して、NativeQueryを作成できます。

entityManager.createNativeQuery(sqlStmt);

結果のマッピングに応じて、前の例で見たように、Entityクラスなどの2番目のパラメーターをメソッドに渡すこともできます。

NativeQueriesにも長所と短所があります。 それらをすばやく見てみましょう。

長所:

  • クエリが複雑になると、JPAで生成されたSQLステートメントが最適化されない場合があります。 この場合、 NativeQueries を使用して、クエリをより効率的にすることができます。
  • NativeQueries を使用すると、データベースベンダー固有の機能を使用できます。 これらの機能により、クエリのパフォーマンスが向上する場合があります

短所:

  • ベンダー固有の機能は利便性とパフォーマンスの向上をもたらす可能性がありますが、あるデータベースから別のデータベースへの移植性を失うことで、そのメリットを享受しています。

6. 基準APIクエリ

Criteria APIクエリは、プログラムで構築されたタイプセーフなクエリであり、構文のJPQLクエリにいくらか似ています。

public UserEntity getUserByIdWithCriteriaQuery(Long id) {
    CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
    CriteriaQuery<UserEntity> criteriaQuery = criteriaBuilder.createQuery(UserEntity.class);
    Root<UserEntity> userRoot = criteriaQuery.from(UserEntity.class);
    UserEntity queryResult = getEntityManager().createQuery(criteriaQuery.select(userRoot)
      .where(criteriaBuilder.equal(userRoot.get("id"), id)))
      .getSingleResult();
    return queryResult;
}

Criteria APIクエリを直接使用するのは困難な場合がありますが、動的クエリ要素を追加する必要がある場合、またはJPAメタモデルと組み合わせる場合に最適です。

7. 結論

この簡単な記事では、JPAクエリとは何かとその使用法について学びました。

JPAクエリは、JPQL構文に依存し、選択したJPAプロバイダーに Query 変換を処理させることができるため、データアクセス層からビジネスロジックを抽象化するための優れた方法です。

この記事で紹介するすべてのコードは、GitHubから入手できます。