はじめに
SELECT句で特定のカラムだけを指定して取得することを射影と呼びますが、全てのカラムを取得すると都合が悪い場合(カラムの数が多すぎる、LOBのような容量の大きなカラムが存在するなど)に便利な手法です。
今回は、Doma のCriteria APIで射影を表現する方法を説明します。
Criteria APIで射影を実現する方法は2つあります。
- エンティティクラスを使って取得する
- タプルクラスを使って取得する
ここで使うDomaのバージョンは2.43.0です。
DomaやCriteria APIの概要についてはDoma入門の他の記事もお読みください。
この記事で使うサンプルコード
簡略化したコードで説明します。完全なサンプルコードをについては下記のプロジェクトを参照してください。
データベースには従業員を表すemployeeテーブルがあります。
create table employee (
id integer not null primary key,
name varchar(255) not null,
age integer not null,
salary integer,
job_type varchar(20),
hiredate timestamp,
department_id integer,
version integer not null,
insertTimestamp timestamp,
updateTimestamp timestamp);
employeeテーブルに対応するEmployeeクラスを用意します。
@Entity(metamodel = @Metamodel)
public class Employee {
@Id
Integer id;
String name;
Age age;
Salary salary;
@Column(name = "JOB_TYPE")
JobType jobType;
LocalDate hiredate;
@Column(name = "DEPARTMENT_ID")
Integer departmentId;
@Version
@Column(name = "VERSION")
Integer version;
LocalDateTime insertTimestamp;
LocalDateTime updateTimestamp;
// getter, setter
}
射影した結果を保持するDTO(ValueObject)を用意します。
public class NameAndSalaryDto {
private final String name;
private final Salary salary;
public NameAndSalaryDto(String name, Salary salary) {
this.name = name;
this.salary = salary;
}
// getter
}
EmployeeRepository
を用意し、このクラスにいくつかメソッドを追加して例を示していきます。
public class EmployeeRepository {
private final Entityql entityql;
private final NativeSql nativeSql;
public EmployeeRepository(Config config) {
this.entityql = new Entityql(config);
this.nativeSql = new NativeSql(config);
}
}
エンティティクラスを使って取得する
これはすでにあるデータ構造を再利用できると言う意味で非常にお手軽な方法です。
selectTo
メソッドを使えば射影した値をエンティティの特定のプロパティにセットできます(その他のプロパティにはnullや初期値がセットされます)。
public List<Employee> selectNameAndSalaryAsEntityList() {
Employee_ e = new Employee_();
return entityql.from(e).selectTo(e, e.name, e.salary).fetch();
}
発行されるSQLは次のものになります。
select t0_.id, t0_.name, t0_.salary from Employee t0_
nameとsalaryの2つのカラムがSELECT句に指定されたSQLになります。指定していない主キーが含まれていますが、これは仕様で、結果セット内においてエンティティの一意性を保証するために使われます。
タプルクラスを使って取得する
Domaは、org.seasar.doma.jdbc.criteria.tuple
パッケージでTuple2
、Tuple3
、...、Tuple9
といった値の組み合わせを表すクラスを提供します。
クラス名の末尾の数字はそのクラスでいくつの値を扱えるかと言うことを表しています。
次はTuple2
クラスを使って2つのカラムを射影する例です。
public List<Tuple2<String, Salary>> selectNameAndSalary() {
Employee_ e = new Employee_();
return nativeSql.from(e).select(e.name, e.salary).fetch();
}
entityql
ではなくnativeSql
を使っていることに注意してください。個別の識別子を持つエンティティを扱う必要がない場合(主キーを含まない結果の取得や集計関数の利用など)はnativeSql
の利用が適しています。
発行されるSQLは次のものになります。
select t0_.name, t0_.salary from Employee t0_
nameとsalaryの2つのカラムのみがSELECT句に指定されたSQLになっています。
Tuple2〜9のクラスをどのレイヤーでどのように利用するかは自由ですが、以下のようにアプリケーションのDTO(もしくはValueObject)に変換するまでの一時的なデータ構造として使うのが綺麗な設計だと思います。
public List<NameAndSalaryDto> selectNameAndSalaryAsNameAndSalaryDtoList() {
Employee_ e = new Employee_();
return nativeSql.from(e).select(e.name, e.salary).stream()
.map(tuple -> new NameAndSalaryDto(tuple.getItem1(), tuple.getItem2()))
.collect(Collectors.toList());
}
10個以上のカラムを指定して受け取る場合はorg.seasar.doma.jdbc.criteria.tuple.Row
インタフェースで表現することもできますが、カラムの数が多い場合は読み取り専用のエンティティクラスを作成したほうがわかりやすいでしょう。
おわりに
Criteria APIで射影を表現する方法を説明しました。
TupleクラスのようなものがJavaの標準にあればそれを使いたいところですが、実際にはないのが悩ましいところですね。
なお、DomaのTuple2〜9クラスはKotlinで使われることも意識しているのでdestructuring declarationに対応しています。そのためKotlinでは次のように扱えます。
val (number, string) = Tuple2(1, "a")