S2JDBC ブラッシュアップ

リリース後のフィードバックを受けて,ちょっと試行錯誤中.

OR

SimpleWhere は文字通り単純な検索条件を組み立てるものなので,あまり過剰な期待をされても困るわけですが,OR を一つ使いたいだけで SimpleWhere が全く使えなくなるのも確かにアレです.
そんなわけで (どんなわけで?),or()and(Where) を追加してみました.


or() は次のように使います.

List<Employee> list =
  jdbcManager
    .from(Employee.class)
    .where(
      new SimpleWhere().lt("salary", 1000).or().gt("salary", 3000))
    .getResultList();

次のような SQL が生成されます.

select ...
from   employee
where  (salary < 1000) or (salary > 3000)

もちっと複雑だとこんな感じ.

List<Employee> list =
  jdbcManager
    .from(Employee.class)
    .where(
      new SimpleWhere()
        .gt("salary", 1000).lt("salary", 2000).or()
        .gt("salary", 3000).lt("salary", 4000).or()
        .gt("salary", 5000).lt("salary", 6000))
    .getResultList();

次のような SQL が生成されます.

select ...
from   employee
where  (salary > 1000 and salary < 2000)
   or  (salary > 3000 and salary < 4000)
   or  (salary > 5000 and salary < 6000)

でもでも,Eclipse が上のようにフォーマットしてくれるわけではないので,あまり複雑になると可読性については微妙かも.
その辺は SQL ファイルとの併用ということでお願い.


and(Where) は次のように使います.

List<Employee> list =
  jdbcManager
    .from(Employee.class)
    .where(
      new SimpleWhere()
        .eq("departmentId", 3).and(
          new SimpleWhere().lt("salary", 1000).or().gt("salary", 3000)))
    .getResultList();

次のような SQL が生成されます.

select ...
from   employee
where  departmentId = 3 and ((salary < 1000) or (salary > 3000))

and(Where) を使わずに,

List<Employee> list =
  jdbcManager
    .from(Employee.class)
    .where(
      new SimpleWhere()
        .eq("departmentId", 3).lt("salary", 1000).or().gt("salary", 3000))
    .getResultList();

としちゃうと次のような SQL が生成されます.

select ...
from   employee
where  (departmentId = 3 and salary < 1000) or (salary > 3000)

SQL と同じで or() の結合度は弱いという扱いなのでこうなる次第.
うーん,もはや SimpleWhere とは呼べないか?
まぁ,評判が芳しくなければ削除するか,別の案を考えるのでご意見お願いします.

EntityCondition

プロパティ名を文字列で指定するのは確かにいけてないので,ちょっと考えてみました.
まずは次のようなクラスを作成します.

public class EmployeeCondition extends
    AbstractEntityCondition<EmployeeCondition> {

  public EmployeeCondition() {
  }

  public EmployeeCondition(String prefix) {
    super(prefix);
  }

  public NotNullableCondition<EmployeeCondition, Integer> employeeId =
    new NotNullableCondition<EmployeeCondition, Integer>("employeeId", this);

  public NullableCondition<EmployeeCondition, Integer> employeeNo =
    new NullableCondition<EmployeeCondition, Integer>("employeeNo", this);

  public NullableStringCondition<EmployeeCondition> employeeName =
    new NullableStringCondition<EmployeeCondition>("employeeName", this);

  public NullableCondition<EmployeeCondition, Integer> managerId =
    new NullableCondition<EmployeeCondition, Integer>("managerId", this);

  public EmployeeCondition manager() {
    return new EmployeeCondition(prefix + "manager.");
  }

  public NullableCondition<EmployeeCondition, Date> hiredate =
    new NullableCondition<EmployeeCondition, Date>("hiredate", this);

  public NullableCondition<EmployeeCondition, BigDecimal> salary =
    new NullableCondition<EmployeeCondition, BigDecimal>("salary", this);

  public NullableCondition<EmployeeCondition, Integer> departmentId =
    new NullableCondition<EmployeeCondition, Integer>("departmentId", this);

  public DepartmentCondition department() {
    return new DepartmentCondition(prefix + "department.");
  }

  public NullableCondition<EmployeeCondition, Integer> addressId =
    new NullableCondition<EmployeeCondition, Integer>("addressId", this);

  public NullableCondition<EmployeeCondition, Integer> version =
    new NullableCondition<EmployeeCondition, Integer>("version", this);
}

ちょっと気持ち悪いコードですが,これを直接見ることはあまりないと考えてるので気にしない.
っていうか,これを人が書くことも考えてないので気にすることはないのだ.


ともあれ (JW),現在は

List<Employee> list =
  jdbcManager.from(Employee.class).where(
    new SimpleWhere().eq("employeeName", "SMITH")).getResultList();

と書いているコードがこう書けるようになります.

List<Employee> list =
  jdbcManager.from(Employee.class).where(
    new EmployeeCondition().employeeName.eq("SMITH")).getResultList();

もちろん,employeeName の所は補完が効くし,eq() の引数は String じゃないとコンパイルエラーになります.

List<Employee> list =
  jdbcManager.from(Employee.class).where(
    new EmployeeCondition().salary.gt(1000).salary.lt(3000)).getResultList();

のように連結することもできるし,上で書いた or()and(Where) を使うこともできます.
ただし,関連がちょっと苦しくて,現状は

List<Department> list =
  jdbcManager
    .from(Department.class)
    .join("employees")
    .join("employees.address")
    .where(new DepartmentCondition().employees().addressId.eq(3))
    .getResultList();

となってしまいます.
通常のプロパティは Condition クラスの public フィールドなのに,関連のプロパティはメソッドになっちゃってるんですよね.
補完でそれしか選べないから躓くことはあまりないと思うけど,こういう不揃いはできれば排除したいところ.
でもでも,どうにもやりようが思いつかなくて...
Java7 で導入されるプロパティを使えれば見た目は揃うんですけどね.


ともあれ (JW),EmployeeCondition のようなクラスさえ用意できれば,プロパティ名も補完できるし,型チェックも強化されるなど,DBFlute の ConditionBean 的なメリットが得られます.
評判がよければ EmployeeCondition のようなクラスは Dolteng がエンティティから自動生成するとか考えてます.

ストアド (21:00 頃追記)

今の S2JDBC はストアドの呼び出しについては SQL の自動生成はサポートしていません.
JdbcManager〜BySql() というメソッドは SQL を指定するもので,call 系は callBySql() しかないのです.
で,ストアド呼び出しも自動生成するとしたら,ってことで案だけ考えてみました.


まずは引数も戻り値もないストアドプロシージャの呼び出し.

jdbcManager.call("myproc").execute();

これはまぁいいでしょう.
引数が IN だけのプロシージャならこんな感じで.

jdbcManager.call("myproc", 1, "foo").execute();


で,OUT や INOUT が出てくるとちょっとやっかいなわけで,こういうのはどうかな,と.

Out<Integer> arg1 = new Out<Integer>();
InOut<String> arg2 = new InOut<String>("hoge");
jdbcManager.call("myproc", arg1, arg2).execute();

呼び出しの後に Out<T>InOut<T>get() メソッドを呼び出すとプロシージャからの値が受け取れる,みたいな.
実際は,値を持ってない Out<T> についてはもっと考えないと実行時に型が取れないんですけどね.


戻り値のあるファンクションの場合は

InOut<String> arg2 = new InOut<String>("hoge");
String result = jdbcManager.call("myfunc", 1, arg2).getSingleResult();

とか何とか.
戻り値が結果セットなら

InOut<String> arg2 = new InOut<String>("hoge");
List<HogeDto> result = jdbcManager.call("myfunc", 1, arg2).getResultList(HogeDto.class);

...
HogeDto って 2 回書くのがカッコ悪いかなぁ.
まぁ,まだまだ案だけなのでこの辺で.
いずれにせよ,ストアドについてはそんなにメリットないかも?
だって全然流れないんだもの.

日付の範囲検索 (03:00 頃追記)

ふーむ.
これは申し訳ないけど魅力を感じないかなぁ.


あ,BETWEEN はあってもいいと思ってます.
今の SimpleWhere には BETWEEN がないのだけど,これは歴史的経緯によるというか,最初は SimpleWhere もなくて,Map で条件を指定してたんだけど一つのキーに値を二つ指定する BETWEEN は指定しにくいのでサポートしてなくて,そこから生まれた SimpleWhere にもないだけじゃないか,というのが taedium さんの推測.
要するに,現状は漏れてるだけじゃないかと思うので,between(String, Object from, Object to) なメソッドを追加するのはありだと思います.


でもでも,日付に限って特殊な扱いをするのはないかなぁ,と.
そもそもこれが嬉しい状況って,DB の方が標準 SQL でいうところの TIMESTAMP 型,つまり日付と時刻を持っていて,問い合わせ条件としては日付だけで指定したいってことですよね?
もし DB の方が標準 SQL でいうところの DATE 型,つまり日付しか持っていなければ,

.where(new SimpleWhere()
  .ge("hireDate", condFromHireDate)
  .le("hireDate", condToHireDate)

ですむはずなので.
なので,日付として扱いたいなら標準 SQL でいうところの DATE にするのが筋なんじゃないかと.


まぁ,Oracle (9i 以前?) みたいに DATE といいつつ日付と時刻を持っていて,日付だけとか時刻だけとかのデータ型を持ってない場合もあるわけで,そういう場合にはもしかしたら重宝する...
かもしれないけれど,自分が過去に関わってきた Oracle なシステムは DATE を使わず文字列で日付を表してたり.


それはそれでどうかという話もあるだろうけど,それを抜きにしても日付って DB アクセスとは独立にリッチなライブラリが必須だったりすると思うんですよね.
翌営業日とか○営業日後とか当月最終営業日とかサクッと求められないと話にならないですよね?
そういうライブラリが揃っていれば,DB アクセスの所で頑張る必要は微塵もなかったりするんじゃないかなぁ.


そんなわけで (どんなわけで?),「日付の範囲検索」について DBFlute 風の機能を付け加えたいとは思わないかな.


ともあれ (JW),まだ発展途上の S2JDBC ですが,よりよいものにしていきたいのでご意見よろしく〜.