はじめに
SQLのWHERE句を組み立てる際、複数の条件を指定するにはANDやORといった論理演算子で連結することになります。この記事では、そのような論理演算子の連結をDoma 2.43.0のCriteria APIではどのように表現できるのかを示します。
論理演算子の連結はSQLで表現するにはシンプルな機能ですが、プログラミング言語としてどのように実現するのかは検討しがいのある問題です。Domaではラムダ式を使って表現できるのが特徴的です。
DomaやCriteria APIの概要についてはDoma入門の他の記事もお読みください。
この記事で使うサンプルコード
データベースには従業員を表す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;
}
EmployeeRepository
を用意し、このクラスのselect
メソッドを書き換えていくつかの例を示していきます。
public class EmployeeRepository {
private final Entityql entityql;
public EmployeeRepository(Config config) {
this.entityql = new Entityql(config);
}
public List<Employee> select() {
// ここを様々に書き換えてコード例を示します
}
}
暗黙的なAND論理演算子
検索条件(この例だとeq(..., ...)
)を並べると暗黙的にANDで連結されます。
Employee_ e = new Employee_();
return entityql.from(e).where(c -> {
c.eq(e.id, 1);
c.eq(e.name, "aaa");
c.eq(e.age, 20);
}).fetch();
生成されるSQLは次のようなものになります。
(以降ではバインドされた値を埋め込んだ状態のSQLを示しますが、実際にはバインド変数の?
を使ったSQLを発行しています。)
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.id = 1 and t0_.name = 'aaa' and t0_.age = 20
明示的なAND論理演算子
先ほどの例ではANDに相当するメソッドを呼び出しませんでしたが、次のようにANDに相当するメソッドand
を明示的に呼び出すことも可能です。and
メソッドはラムダ式を受け取ります。
Employee_ e = new Employee_();
return entityql.from(e).where(c -> {
c.eq(e.id, 1);
c.and(() -> c.eq(e.name, "aaa"));
c.and(() -> c.eq(e.age, 20));
}).fetch();
生成されるSQLは次のようになり先ほどの例と同じSQLが生成されます(ただし、カッコの有無といった差異はあります)。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.id = 1 and (t0_.name = 'aaa') and (t0_.age = 20)
OR論理演算子
ORで連結するには、次のようにor
メソッドを呼び出します。or
メソッドはラムダ式を受け取ります。
Employee_ e = new Employee_();
return entityql.from(e).where(c -> {
c.eq(e.id, 1);
c.or(() -> {
c.eq(e.name, "aaa");
c.eq(e.age, 20);
});
}).fetch();
生成されるSQLは次のようになります。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.id = 1 or (t0_.name = 'aaa' and t0_.age = 20)
先頭のANDやORの自動除去機能
直前の例と似ていますが、次のコードはどのようなSQLを生成するでしょうか?
Employee_ e = new Employee_();
return entityql.from(e).where(c -> {
c.or(() -> {
c.eq(e.name, "aaa");
c.eq(e.age, 20);
});
c.eq(e.id, 1);
}).fetch();
WHERE句の直後に余分なORが生成されてしまうと思うかもしれませんが、DomaはWHERE句の先頭のANDやORを自動除去するので次のようなSQLが生成されます。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where (t0_.name = 'aaa' and t0_.age = 20) and t0_.id = 1
この機能は、WHERE句に条件分岐が入るなどでANDやORの要不要が静的に決まらない場合に便利です。
Employee_ e = new Employee_();
return entityql.from(e).where(c -> {
if (何かの条件) {
c.or(() -> {
c.eq(e.name, "aaa");
c.eq(e.age, 20);
});
}
if (何かの条件2) {
c.or(() -> {
c.eq(e.name, "bbb");
c.eq(e.age, 30);
});
}
}).fetch();
ANDやORのネスト
and
やor
は制限なくいくつもネストできます。
Employee_ e = new Employee_();
return entityql.from(e).where(c -> {
c.eq(e.id, 1);
c.or(() -> {
c.eq(e.name, "aaa");
c.eq(e.age, 20);
c.or(() -> {
c.eq(e.name, "bbb");
c.eq(e.age, 30);
});
});
}).fetch();
上記のコードから次のようなSQLが生成されます。
select t0_.id, t0_.name, t0_.age, t0_.version from Employee t0_ where t0_.id = 1 or (t0_.name = 'aaa' and t0_.age = 20 or (t0_.name = 'bbb' and t0_.age = 30))
おわりに
論理演算子の連結をDomaのCriteria APIではどのように表現できるのかを示しました。
冒頭でも述べたようにDomaのCriteira APIでは、論理演算子の連結をラムダ式で表現しています。他の表現方法としてはメソッドチェーンがありますが、個人的にはメソッドーチェーンには弱点があると思っています。それは条件分岐に弱いということです。
条件分岐があるとチェーンが切れてしまい一挙に煩雑さが増大します。チェーンをつなぐためにチェーンする対象のオブジェクトを変数に保存する必要があるからです。
一方、DomaのCriteria APIのようなラムダ式を使う方法では、条件分岐が入る場合と入らない場合で書き方に差が出ないので(ただifのブロックで囲まれかどうかなので)シンプルで可読性が高いと思います。