DSLQueryとはどのようなもので、どのように使うことができるのか?

はじめに

DSLQueryは、 Domain Specific Language Queryの頭文字をとったものです。 実装にドメイン固有の言語を使用することを基本としています。 これは、 DynamicQuery の実装のために作られたものよりも、クエリなどのロジックをより自然に書くことができそうです。

DSLQueryは7.4に完全に統合され、Service Builderでも7.4用のDSLQueryクラスが生成されるようになった。 Liferay のコードが com.liferay.petra.sql.dsl パッケージのクラスを使用しているときは、常に DSLQuery が実行されます。

DSLQueryとはどのようなもので、どのように使うことができるのか?

テーブル

新しいサービスビルダープロジェクトが開始されると、 service.xml Foo のエンティティが定義されます。 サービスだけ先に作っておけば、それで最初の重要なクラスが来る。

apiモジュールを確認し、生成されたモデルパッケージ(以下に示す例では com.liferay.docs.servicebuilder.model)に移動し、 FooTable クラスを開いてみてください:

package com.liferay.docs.servicebuilder.model;

import com.liferay.petra.sql.dsl.Column;
import com.liferay.petra.sql.dsl.base.BaseTable;

import java.sql.Types;

import java.util.Date;

/**
 * The table class for the "FOO_Foo" database table.
 *
 * @author Brian Wing Shun Chan
 * @see Foo
 * @generated
 */
public class FooTable extends BaseTable {

	public static final FooTable INSTANCE = new FooTable();

	public final Column uuid = createColumn(
		"uuid_", String.class, Types.VARCHAR, Column.FLAG_DEFAULT);
	public final Column fooId = createColumn(
		"fooId", Long.class, Types.BIGINT, Column.FLAG_PRIMARY);
	public final Column groupId = createColumn(
		"groupId", Long.class, Types.BIGINT, Column.FLAG_DEFAULT);
	public final Column companyId = createColumn(
		"companyId", Long.class, Types.BIGINT, Column.FLAG_DEFAULT);
	public final Column userId = createColumn(
		"userId", Long.class, Types.BIGINT, Column.FLAG_DEFAULT);
	public final Column userName = createColumn(
		"userName", String.class, Types.VARCHAR, Column.FLAG_DEFAULT);
	public final Column createDate = createColumn(
		"createDate", Date.class, Types.TIMESTAMP, Column.FLAG_DEFAULT);
	public final Column modifiedDate = createColumn(
		"modifiedDate", Date.class, Types.TIMESTAMP, Column.FLAG_DEFAULT);
	public final Column field1 = createColumn(
		"field1", String.class, Types.VARCHAR, Column.FLAG_DEFAULT);
	public final Column field2 = createColumn(
		"field2", Boolean.class, Types.BOOLEAN, Column.FLAG_DEFAULT);
	public final Column field3 = createColumn(
		"field3", Integer.class, Types.INTEGER, Column.FLAG_DEFAULT);
	public final Column field4 = createColumn(
		"field4", Date.class, Types.TIMESTAMP, Column.FLAG_DEFAULT);
	public final Column field5 = createColumn(
		"field5", String.class, Types.VARCHAR, Column.FLAG_DEFAULT);

	private FooTable() {
		super("FOO_Foo", FooTable::new);
	}

}

重要なのは、エンティティ固有のクラスであること、 INSTANCE シングルトン変数を持つこと、そしてテーブルの各カラムに対して Column フィールドを持つことである。

このクラスは、 [re-]、テーブルを作成するためのSQLコマンドを生成できるなど、さまざまな用途があります。 これを使えば、昔のDynamicQueryのロジックを使うよりも簡単にDSLQueryベースのクエリを定義することができるのです。

クエリ

-apiモジュールの FooLocalService クラスを確認すると、DSLQueryをサポートするための新しいメソッドがいくつか見つかります:

import com.liferay.petra.sql.dsl.query.DSLQuery;
    
...
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	public <T> T dslQuery(DSLQuery dslQuery);

	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	public int dslQueryCount(DSLQuery dslQuery);

これらのメソッドは、以前の dynamicQuery() に似ていますが、新しいオブジェクト、 DSLQueryを受け取ります。

次のステップでは、 DSLQuery のインスタンスを作成する方法について説明します。

クエリーの書き方

ここからは、 FooTableを使用します。 テーブル と、それに含まれる カラムを手に入れたら、次はクエリーの構築を開始します。

簡単なものから始めると、特定のグループIDに所属しているFooのすべてです:

public List getFoosByGroupId(final long groupId) {

  DSLQuery query = DSLQueryFactoryUtil
    .select()
    .from(FooTable.INSTANCE)
    .where(FooTable.INSTANCE.groupId.eq(groupId));

  return dslQuery(query);
}

DSLQueryFactoryUtil は、 SELECT コマンドを作成するために 使用されます。 なお、重複の除去を期待する場合は、 selectDistinct() メソッドも備えています。 ここで使われている引数なしの方法は、基本的に SELECT *と同じですが、1つ以上の式を取る方法があるので、集約やそのような性質のものを扱うことができます(サポートされている式については com.liferay.petra.sql.dsl.DSLFunctionFactoryUtil を確認してください)。

次のステップでは、選択するテーブルを指定します。この場合、 FooTable.INSTANCE.

この例では、 FooTable.INSTANCE.groupId カラムと、 eq() 式が使用されています。 メソッドで渡された groupId パラメータにマッチしているのです。

DSLQuery インスタンスを構築した後、 dslQuery() メソッドを使用すると、クエリを呼び出して結果を返すことができます。 この場合、 Foosのリストが期待されます。

これで、複雑なクエリを扱うことができるようになりました。 DSLQuery は、ユニオン、ジョイン、エイリアス、関数、複雑なwhere句、「group bys」「sort bys」などを扱うことができ、すべてこのタイプのドメイン固有言語 を使ってクエリを構築する。

これの素晴らしいところは、IDEがクエリの作り込みまでサポートしてくれることです。

なぜ、この方がいいのでしょうか?

エラーの発生が少ない

まず何よりも、 DSLQuery は、 DynamicQueryよりもエラーが発生しにくい。 もしユーザーが上記の方法をDQ実装で作ろうとしたら

public List getFoosByGroupId(final long groupId) {
  DynamicQuery dq = dynamicQuery();
		
  dq.add(RestrictionsFactoryUtil.eq("groupid", groupId));
		
  return dynamicQuery(dq);
}

ここでは、 DynamicQuery インスタンスを取得し、 groupIdに制限を加え、 dynamicQuery() メソッドを呼び出しています。

groupId" の代わりに "groupid" という間違ったカラム名を使用していることに注意してください( Iの文字の大文字小文字の変更に注意してください)。 カラム名は単なる文字列なので、このバグをキャッチするのに時間がかかりすぎ、発生した例外を解決するのが難しいかもしれません。

DSLQuery は DSL に基づいているため、ユーザが実際に下手な名前の列の文字列値を入力することはできず、DSL はユーザに groupId を正しく使うよう強制します。

クラスローダーの懸念点

上記のコードからわかるように、クラスローダーに関する懸念はなかった。 DSLQuery は異なる方法で動作します(DynamicQuery は、実際には同様の Hibernate 機能にオーバーレイしていたため、クラスローダーの問題を引き起こしたのは Hibernate とそのプロキシでした)。したがって、クラスローダーの懸念はもはや問題ではありません。

フルサービスビルダーサポート

つまり、ポータルのサービスからユーザーのカスタムSBサービスまで、すべてのSBコードがすでに DSLQuery をサポートしているため、ユーザーはコアエンティティとは異なる独自のエンティティを処理する必要がない。

完全なクエリ構築

上記の例ではあまり明らかではありませんが、スクリーンショットを見ると、 完全な クエリ、 複雑な クエリも DSLQuery を使って構築できるようになり、それ自体がエラーを起こしやすいカスタム SQL 文字列を構築する必要がなくなったことが分かると思います。

コンパイル時検証

DSLQuery エラーが出にくいのはなぜ? Javaのクラスやインターフェイスで実装されているため、これまで実行時に失敗する可能性があったクエリを、コンパイル時に検証することができます。

まとめ

DSLQuery は、Service Builder のすべてのエンティティのカスタムクエリを処理するための最良の方法となりました。 DynamicQuery やカスタムクエリ実装で発生するような問題は軽減されています。

この記事は役に立ちましたか?
1人中1人がこの記事が役に立ったと言っています