はじめに
データベースアクセスライブラリの評価項目はいくつかありますが、その中でも動的なSQLをどれだけ組み立てやすいかというのは優先度が高い項目なのではと思います。静的なSQLは簡単に書けるのに、動的なSQLとなると途端に大変になってしまうというのはありがちですね。特に業務系のアプリケーションの場合、画面に複数の検索項目があり、指定されていたら検索条件に含め指定されていなければ含めないというような要件がありますが、ライブラリが提供する機能によってはアプリケーションのコードが条件分岐だらけになり可読性が低下してしまいます。
この記事ではDoma 2.43.0のCriteria APIを使って動的にSQLのWHERE句を組み立てる機能を紹介し、Domaでは上述した問題をどのように解決できるかを示します。
DomaやCriteria APIの概要については、Doma入門 - Criteria APIの紹介 もお読みください。
この記事で使うサンプルコード
データベースには従業員を表すemployeeテーブルが1つだけあります。
create table employee (
id integer not null primary key,
name varchar(255) not null,
age integer not null,
version integer not null);
employeeテーブルに対応するエンティティクラスとしてEmployee
クラスを用意します。
@Entity(metamodel = @Metamodel)
public class Employee {
@Id
public Integer id;
public String name;
public Integer age;
@Version public Integer version;
}
従業員を年齢で検索するRepositoryとしてEmployeeRepository
を用意します。
検索する際、年齢の上限と下限をオプションで指定できる(指定されていれば検索条件に含めるし、指定されなければ含めない)ものとし、この機能はselectByAgeRange
メソッドで実装します。
public class EmployeeRepository {
private final Entityql entityql;
public EmployeeRepository(Config config) {
this.entityql = new Entityql(config);
}
public List<Employee> selectByAgeRange(Integer min, Integer max) {
// ここをCriteria APIでどのように実装するかがこの記事の主眼です
}
}
nullと比較する検索条件の自動除去機能
DomaのCriteria APIは、nullと比較するような検索条件を指定するとその条件を自動でWHERE句から除外する機能を持ちます。したがって、この機能を利用するとselectByAgeRange
の実装では条件分岐を記述する必要がありません。次のように実装できます。
public List<Employee> selectByAgeRange(Integer min, Integer max) {
Employee_ e = new Employee_();
return entityql
.from(e)
.where(
c -> {
c.ge(e.age, min);
c.le(e.age, max);
})
.fetch();
}
以下では、このメソッドをいくつかのパターンで呼び出した時に生成されるSQLを見てみます。
minにもmaxにも非nullな値を渡す場合
以下、repository
はEmployeeRepository
クラスのインスタンスを表します。
List<Employee> list = repository.selectByAgeRange(30, 40);
この時生成されるSQLは次のようなものになります。年齢の上限と下限を指定した検索条件がちゃんとWHERE句に現れていますね。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.age >= ? and t0_.age <= ?
minに非null、maxにnullを渡す場合
List<Employee> list = repository.selectByAgeRange(30, null);
上限を指定する検索条件がWHERE句に現れません。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.age >= ?
minにnull、maxに非nullを渡す場合
List<Employee> list = repository.selectByAgeRange(null, 40);
今度は下限を指定する検索条件がWHERE句に現れません。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.age <= ?
minにもmaxにもnullを渡す場合
List<Employee> list = repository.selectByAgeRange(null, null);
どういう結果になるか予想できると思います。そう、上限と下限を指定する検索条件がどちらも現れなくなります。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_
ブロック内での明示的な条件分岐
では、nullではない値を見て検索条件に含めるかどうか判断したい場合はどのように書くべきでしょうか?例えば、年齢の下限が0以下の時は条件に含めない(0より大きい場合にのみ条件に含める)という要件があったとします。その場合は次のように明示的な条件分岐を書けます。
public List<Employee> selectByAgeRange(Integer min, Integer max) {
Employee_ e = new Employee_();
return entityql
.from(e)
.where(
c -> {
if (min != null && min > 0) {
c.ge(e.age, min);
}
c.le(e.age, max);
})
.fetch();
}
単にwhereメソッドに渡すラムダ式のブロックの中で条件分岐をしています。条件が評価されない限りは検索条件に含まれません。
List<Employee> list = repository.selectByAgeRange(-1, 40);
上記のように呼び出すと、次のようなSQLが生成され、下限を指定する検索条件がWHERE句に登場しないことがわかります。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.age <= ?
おわりに
この記事ではDomaのCriteria APIを使って簡潔にSQLの動的なWHERE句を組み立てられることを紹介しました。
ここに示したの全く同じではないのですが、似たようなコードが下記のプロジェクトにあり動かして試すことができます。