Querydslの概要
1. 前書き
これは、データ永続性のための強力なQuerydslAPIを使用して稼働させるための入門記事です。
ここでの目標は、Querydslをプロジェクトに追加し、生成されたクラスの構造と目的を理解し、ほとんどの一般的なシナリオでタイプセーフなデータベースクエリを記述する方法の基本を理解する実用的なツールを提供することです。
2. Querydslの目的
オブジェクトリレーショナルマッピングフレームワークは、エンタープライズJavaの中核です。 これらは、オブジェクト指向アプローチとリレーショナルデータベースモデルの不一致を補います。 また、開発者は、より簡潔で簡潔な永続化コードとドメインロジックを作成できます。
ただし、ORMフレームワークの最も難しい設計選択の1つは、正確でタイプセーフなクエリを作成するためのAPIです。
最も広く使用されているJava ORMフレームワークの1つであるHibernate(および密接に関連するJPA標準)は、SQLに非常によく似た文字列ベースのクエリ言語HQL(JPQL)を提案しています。 このアプローチの明らかな欠点は、型の安全性の欠如と静的クエリチェックの欠如です。 また、より複雑な場合(たとえば、いくつかの条件に応じて実行時にクエリを構築する必要がある場合)、HQLクエリの構築には、通常非常に安全でエラーが発生しやすい文字列の連結が含まれます。
JPA 2.0標準は、Criteria Query APIの形式で改善をもたらしました。これは、アノテーションの前処理中に生成されたメタモデルクラスを利用したクエリを構築する新しいタイプセーフな方法です。 残念なことに、Criteria Query APIは本質的に画期的であるため、非常に冗長で、事実上読み取り不能になりました。 SELECT p FROM Pet pのような単純なクエリを生成するためのJavaEEチュートリアルの例を次に示します。
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Pet.class);
Root pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery q = em.createQuery(cq);
List allPets = q.getResultList();
生成されたメタデータクラスの同じアイデアに基づいて、流暢で読みやすいAPIで実装された、より適切なQuerydslライブラリがすぐに登場したのも不思議ではありません。
3. Querydslクラスの生成
Querydslの流れるようなAPIを説明する魔法のメタクラスの生成と探索から始めましょう。
3.1. QuerydslをMavenビルドに追加する
Querydslをプロジェクトに含めるのは、ビルドファイルにいくつかの依存関係を追加し、JPAアノテーションを処理するためのプラグインを構成するだけです。 依存関係から始めましょう。 Querydslライブラリのバージョンは、次のように、<project><properties>セクション内の別のプロパティに抽出する必要があります(Querydslライブラリの最新バージョンについては、Maven Centralリポジトリを確認してください)。
4.1.3
次に、次の依存関係をpom.xmlファイルの<project><dependencies>セクションに追加します。
com.querydsl
querydsl-apt
${querydsl.version}
provided
com.querydsl
querydsl-jpa
${querydsl.version}
querydsl-apt依存関係は、注釈処理ツール(APT)—対応するJava APIの実装であり、コンパイル段階に進む前にソースファイル内の注釈を処理できます。 このツールは、いわゆるQタイプを生成します。これは、アプリケーションのエンティティクラスに直接関連するクラスですが、先頭に文字Qが付きます。 たとえば、アプリケーションに@EntityアノテーションでマークされたUserクラスがある場合、生成されたQタイプはQUser.javaソースファイルに存在します。
querydsl-apt依存関係のprovidedスコープは、このjarをビルド時にのみ使用可能にする必要があり、アプリケーションアーティファクトには含めないことを意味します。
querydsl-jpaライブラリは、JPAアプリケーションと一緒に使用するように設計されたQuerydsl自体です。
querydsl-aptを利用するアノテーション処理プラグインを構成するには、次のプラグイン構成をpomに追加します–<project><build><plugins>要素内:
com.mysema.maven
apt-maven-plugin
1.1.3
process
target/generated-sources/java
com.querydsl.apt.jpa.JPAAnnotationProcessor
このプラグインは、Mavenビルドのプロセス目標の間にQタイプが生成されるようにします。 outputDirectory構成プロパティは、Qタイプのソースファイルが生成されるディレクトリを指します。 このプロパティの値は、後でQファイルを調べるときに役立ちます。
IDEが自動的にこれを行わない場合は、プロジェクトのソースフォルダーにもこのディレクトリを追加する必要があります。その方法については、お気に入りのIDEのドキュメントを参照してください。
この記事では、ブログサービスの単純なJPAモデルを使用します。これは、UsersとそのBlogPostsで構成され、それらの間には1対多の関係があります。
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String login;
private Boolean disabled;
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
private Set blogPosts = new HashSet<>(0);
// getters and setters
}
@Entity
public class BlogPost {
@Id
@GeneratedValue
private Long id;
private String title;
private String body;
@ManyToOne
private User user;
// getters and setters
}
モデルのQタイプを生成するには、単に次を実行します。
mvn compile
3.2. 生成されたクラスの探索
次に、apt-maven-pluginのoutputDirectoryプロパティ(この例ではtarget/generated-sources/java)で指定されたディレクトリに移動します。 すべてのクラスが文字Q(この場合はQUserとQBlogPost)で始まることを除いて、ドメインモデルを直接ミラーリングするパッケージとクラス構造が表示されます。
ファイルQUser.javaを開きます。 これは、ルートエンティティとしてUserを持つすべてのクエリを構築するためのエントリポイントです。 最初に気付くのは、@Generatedアノテーションです。これは、このファイルが自動的に生成されたため、手動で編集しないでください。 ドメインモデルクラスのいずれかを変更した場合は、mvn compileを再度実行して、対応するすべてのQタイプを再生成する必要があります。
このファイルに存在するいくつかのQUserコンストラクターの他に、QUserクラスのパブリック静的最終インスタンスにも注意する必要があります。
public static final QUser user = new QUser("user");
これは、このエンティティに対するほとんどのQuerydslクエリで使用できるインスタンスです。ただし、1つのクエリでテーブルの複数の異なるインスタンスを結合するなど、より複雑なクエリを記述する必要がある場合を除きます。
最後に注意する必要があるのは、エンティティクラスのすべてのフィールドに、NumberPath id、StringPath login、SetPath blogPostsなどの対応する*PathフィールドがQタイプにあることです。 QUserクラス内(Setに対応するフィールドの名前が複数形になっていることに注意してください)。 これらのフィールドは、後で遭遇するFluent Query APIの一部として使用されます。
4. Querydslを使用したクエリ
4.1. 簡単なクエリとフィルタリング
クエリを作成するには、最初にJPAQueryFactoryのインスタンスが必要です。これは、作成プロセスを開始するための推奨される方法です。 JPAQueryFactoryに必要なのはEntityManagerだけです。これは、EntityManagerFactory.createEntityManager()呼び出しまたは@PersistenceContextインジェクションを介してJPAアプリケーションですでに使用可能になっているはずです。
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("org.example.querydsl.intro");
EntityManager em = entityManagerFactory.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
最初のクエリを作成しましょう:
QUser user = QUser.user;
User c = queryFactory.selectFrom(user)
.where(user.login.eq("David"))
.fetchOne();
ローカル変数QUserユーザーを定義し、QUser.user静的インスタンスで初期化したことに注意してください。 これは単に簡潔にするために行われます。あるいは、静的なQUser.userフィールドをインポートすることもできます。
JPAQueryFactoryのselectFromメソッドは、クエリの作成を開始します。 これにQUserインスタンスを渡し、.where()メソッドを使用してクエリの条件節を作成し続けます。 user.loginは、これまでに見たQUserクラスのStringPathフィールドへの参照です。 StringPathオブジェクトには.eq()メソッドもあり、フィールドの等価条件を指定することで、クエリの作成をスムーズに続行できます。
最後に、データベースから永続コンテキストに値をフェッチするために、fetchOne()メソッドの呼び出しで構築チェーンを終了します。 このメソッドは、オブジェクトが見つからない場合はnullを返しますが、.where()条件を満たすエンティティが複数ある場合はNonUniqueResultExceptionをスローします。
4.2. 注文とグループ化
次に、リスト内のすべてのユーザーを取得し、ログイン順に昇順で並べ替えます。
List c = queryFactory.selectFrom(user)
.orderBy(user.login.asc())
.fetch();
*Pathクラスには.asc()メソッドと.desc()メソッドがあるため、この構文が可能です。 .orderBy()メソッドに複数の引数を指定して、複数のフィールドで並べ替えることもできます。
それでは、もっと難しいことを試してみましょう。 すべての投稿をタイトルでグループ化し、重複するタイトルをカウントする必要があるとします。 これは、.groupBy()句を使用して行われます。 また、結果の出現回数に基づいてタイトルを並べる必要があります。
NumberPath count = Expressions.numberPath(Long.class, "c");
List userTitleCounts = queryFactory.select(
blogPost.title, blogPost.id.count().as(count))
.from(blogPost)
.groupBy(blogPost.title)
.orderBy(count.desc())
.fetch();
ブログ投稿のタイトルと重複の数を選択し、タイトルでグループ化し、集計された数で並べ替えました。 .orderBy()句で参照する必要があるため、最初に。select()句でcount()フィールドのエイリアスを作成したことに注意してください。
4.3. 結合とサブクエリを含む複雑なクエリ
「Hello World!」というタイトルの投稿を書いたすべてのユーザーを見つけましょう。そのようなクエリには、内部結合を使用できます。 結合されたテーブルのエイリアスblogPostを作成して、.on()句で参照していることに注意してください。
QBlogPost blogPost = QBlogPost.blogPost;
List users = queryFactory.selectFrom(user)
.innerJoin(user.blogPosts, blogPost)
.on(blogPost.title.eq("Hello World!"))
.fetch();
次に、サブクエリを使用して同じことを実現してみましょう。
List users = queryFactory.selectFrom(user)
.where(user.id.in(
JPAExpressions.select(blogPost.user.id)
.from(blogPost)
.where(blogPost.title.eq("Hello World!"))))
.fetch();
ご覧のとおり、サブクエリはクエリと非常によく似ており、非常に読みやすくなっていますが、JPAExpressionsのファクトリメソッドで始まります。 いつものように、サブクエリをメインクエリに接続するには、以前に定義して使用したエイリアスを参照します。
4.4. データの変更
JPAQueryFactoryを使用すると、クエリを作成できるだけでなく、レコードを変更および削除することもできます。 ユーザーのログインを変更して、アカウントを無効にしましょう。
queryFactory.update(user)
.where(user.login.eq("Ash"))
.set(user.login, "Ash2")
.set(user.disabled, true)
.execute();
さまざまなフィールドに必要な.set()句をいくつでも含めることができます。 .where()句は不要なので、すべてのレコードを一度に更新できます。
特定の条件に一致するレコードを削除するには、同様の構文を使用できます。
queryFactory.delete(user)
.where(user.login.eq("David"))
.execute();
.where()句も必要ありませんが、.where()句を省略すると、特定のタイプのエンティティがすべて削除されるため、注意が必要です。
なぜJPAQueryFactoryに.insert()メソッドがないのか不思議に思うかもしれません。 これは、JPA Queryインターフェイスの制限です。 基になるjavax.persistence.Query.executeUpdate()メソッドは、更新および削除を実行できますが、挿入ステートメントは実行できません。 データを挿入するには、EntityManagerでエンティティを単純に永続化する必要があります。
データを挿入するために同様のQuerydsl構文を引き続き利用する場合は、querydsl-sqlライブラリにあるSQLQueryFactoryクラスを使用する必要があります。
5. 結論
この記事では、Querydslによって提供される永続オブジェクト操作用の強力でタイプセーフなAPIを発見しました。
Querydslをプロジェクトに追加し、生成されたQタイプを調査する方法を学びました。 また、いくつかの典型的なユースケースも取り上げ、簡潔さと読みやすさを楽しんでいます。
例のすべてのソースコードはgithub repositoryにあります。
最後に、もちろん、生のSQL、非永続コレクション、NoSQLデータベース、全文検索の操作など、Querydslが提供する機能は他にもたくさんあります。これらのいくつかについては、今後の記事で説明します。